Tier 1 Deep Dive: Core LLM Techniques
================================================================================
TIER 1 DEEP DIVE: CORE LLM TECHNIQUES
Prerequisites: Transformer fundamentals, SFT (assumed known from nanoGPT + Dolly)
Topics Covered:
- RLHF (Reinforcement Learning from Human Feedback)
- DPO (Direct Preference Optimization)
- RoPE (Rotary Position Embeddings)
- GQA (Grouped Query Attention)
- KV Cache
- LoRA (Low-Rank Adaptation)
- Quantization
- RAG (Retrieval Augmented Generation)
- Chain-of-Thought (CoT)
################################################################################
1. RLHF - REINFORCEMENT LEARNING FROM
HUMAN FEEDBACK
################################################################################
WHAT IS IT?
───────────
RLHF is the technique that transformed GPT-3 into ChatGPT. It aligns language
models to human preferences using reinforcement learning, making them helpful,
harmless, and honest.
WHY IT MATTERS
──────────────
- Base LLMs just predict next tokens - they don't know what humans actually want
- SFT teaches format but not nuanced preferences
- RLHF teaches the model to optimize for human satisfaction
- This is THE key innovation behind ChatGPT's success
THE THREE-STAGE PIPELINE
────────────────────────
┌─────────────────────────────────────────────────────────────────────────────┐
│ Stage 1: Supervised Fine-Tuning (SFT) │
│ ───────────────────────────────────── │
│ • Start with pretrained base model │
│ • Fine-tune on high-quality demonstration data │
│ • Human labelers write ideal responses to prompts │
│ • Standard cross-entropy loss │
│ • Result: Model that can follow instructions (you did this!) │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Stage 2: Reward Model Training │
│ ───────────────────────────────── │
│ • Collect comparison data: humans rank multiple outputs │
│ • For prompt X, model generates responses A and B │
│ • Human says: "A is better than B" (or vice versa) │
│ • Train reward model to predict human preferences │
│ │
│ Architecture: │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ Prompt │───▶│ LLM │───▶│ Scalar │ (reward score) │
│ │+Response │ │ (frozen) │ │ Head │ │
│ └──────────┘ └──────────┘ └────────┘ │
│ │
│ Loss function (Bradley-Terry model): │
│ L = -log(σ(r(x,y_w) - r(x,y_l))) │
│ │
│ Where: │
│ r(x,y) = reward for response y given prompt x │
│ y_w = preferred (winning) response │
│ y_l = rejected (losing) response │
│ σ = sigmoid function │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Stage 3: RL Fine-Tuning with PPO │
│ ──────────────────────────────── │
│ • Use reward model to score generations │
│ • Optimize policy (the LLM) to maximize reward │
│ • PPO (Proximal Policy Optimization) prevents too-large updates │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Prompt │────▶│ Policy │────▶│ Response │────▶│ Reward │ │
│ │ │ │ (LLM) │ │ │ │ Model │ │
│ └──────────┘ └──────────┘ └──────────┘ └────┬─────┘ │
│ ▲ │ │
│ │ ┌──────────┐ │ │
│ └─────────│ PPO │◀──────────┘ │
│ update │ Optimize │ reward │
│ └──────────┘ │
│ │
│ Key components: │
│ • Policy: The LLM being trained │
│ • Reference model: Frozen copy of SFT model (prevents drift) │
│ • KL penalty: Keeps policy close to reference │
│ │
│ Objective: │
│ maximize E[r(x,y)] - β * KL(π_θ || π_ref) │
│ │
│ The KL term prevents reward hacking (gaming the reward model) │
└─────────────────────────────────────────────────────────────────────────────┘
PPO SPECIFICS
─────────────
PPO is used because it's stable and sample-efficient:
L_PPO = min(r_t(θ) * A_t, clip(r_t(θ), 1-ε, 1+ε) * A_t)
Where:
- r_t(θ) = π_θ(a|s) / π_old(a|s) (probability ratio)
- A_t = advantage estimate
- ε = clipping parameter (typically 0.2)
The clipping prevents the policy from changing too drastically in one update.
PRACTICAL CONSIDERATIONS
────────────────────────
• Reward hacking: Model finds exploits in reward model
- Solution: KL penalty, reward model ensembles
• Labeler disagreement: Humans often disagree - Solution: Multiple labelers, clear guidelines
• Compute intensive: Need to run inference during training - Solution: Efficient batching, model parallelism
• Mode collapse: Model outputs become repetitive - Solution: Entropy bonus, diverse prompts
KEY PAPERS
──────────
• "Training language models to follow instructions with human feedback" (2022)
- The InstructGPT paper from OpenAI
• "Learning to summarize from human feedback" (2020) - Earlier work that laid groundwork
################################################################################
2. DPO - DIRECT PREFERENCE OPTIMIZATION
################################################################################
WHAT IS IT?
───────────
DPO is a simpler alternative to RLHF that skips the reward model entirely.
It directly optimizes the language model on preference data.
WHY IT MATTERS
──────────────
• RLHF is complex: 3 models (policy, reference, reward), PPO is finicky
• DPO achieves similar results with just supervised learning
• Much easier to implement and debug
• More stable training dynamics
• Becoming the preferred method for many practitioners
THE KEY INSIGHT
───────────────
The optimal policy under RLHF has a closed-form solution!
Given reward r(x,y) and reference policy π_ref:
π*(y|x) = (1/Z(x)) * π_ref(y|x) * exp(r(x,y)/β)
This means we can express the reward in terms of policies:
r(x,y) = β * log(π*(y|x) / π_ref(y|x)) + β * log(Z(x))
DPO substitutes this into the Bradley-Terry preference model and derives
a loss that only depends on the policy (no explicit reward model needed).
DPO LOSS FUNCTION
─────────────────
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ L_DPO(π_θ; π_ref) = -E[ log σ( β * (log π_θ(y_w|x)/π_ref(y_w|x) │
│ - log π_θ(y_l|x)/π_ref(y_l|x)) ) ] │
│ │
│ Simplified: │
│ L = -log σ( β * (Δlog_prob_winner - Δlog_prob_loser) ) │
│ │
│ Where: │
│ - y_w = preferred response │
│ - y_l = rejected response │
│ - β = temperature parameter (controls deviation from reference) │
│ - Δlog_prob = log π_θ(y|x) - log π_ref(y|x) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
INTUITION
─────────
The loss increases the probability of preferred responses while decreasing
the probability of rejected responses, relative to the reference model.
• If model prefers winner over loser → loss is low ✓
• If model prefers loser over winner → loss is high ✗
The reference model acts as an anchor to prevent the model from
degenerating (e.g., assigning all probability to one token).
TRAINING PROCEDURE
──────────────────
- Start with SFT model (this becomes both π_θ and π_ref initially)
- Freeze a copy as π_ref
- For each preference pair (x, y_w, y_l):
a. Compute log probs under π_θ for both responses
b. Compute log probs under π_ref for both responses (cached, no grad)
c. Compute DPO loss
d. Update π_θ
COMPARISON: RLHF vs DPO
───────────────────────
┌─────────────────────────────────────────────────────────────────────────────┐
│ RLHF DPO │
├─────────────────────────────────────────────────────────────────────────────┤
│ Models needed 3 (policy, ref, reward) 2 (policy, ref) │
│ Training RL (PPO) - unstable Supervised - stable │
│ Hyperparameters Many (PPO has ~10+) Few (mainly β) │
│ Compute High (online generation) Lower (offline) │
│ Implementation Complex Simple │
│ Performance Gold standard Comparable │
│ Data efficiency Can use same data many Fixed dataset │
│ times via online sampling │
└─────────────────────────────────────────────────────────────────────────────┘
VARIANTS
────────
• IPO (Identity Preference Optimization): Removes log sigmoid for stability
• KTO (Kahneman-Tversky Optimization): Works with just good/bad labels
• ORPO: Combines SFT and preference optimization in one step
• SimPO: Simplified version without reference model
PRACTICAL TIPS
──────────────
• β typically 0.1 to 0.5 (higher = more conservative)
• Learning rate: lower than SFT (1e-6 to 1e-5)
• Batch size matters: larger is generally better
• Data quality > quantity for preference pairs
• Reference model can be periodically updated (iterative DPO)
KEY PAPER
─────────
• "Direct Preference Optimization: Your Language Model is Secretly a Reward Model" (2023)
################################################################################
3. RoPE - ROTARY POSITION EMBEDDINGS
################################################################################
WHAT IS IT?
───────────
RoPE encodes position information by rotating the query and key vectors
in attention. It's used in most modern LLMs (Llama, Mistral, etc.).
WHY IT MATTERS
──────────────
• Original transformer used absolute position embeddings (limited)
• RoPE provides relative position information naturally
• Better extrapolation to longer sequences than seen in training
• Enables efficient long-context extensions
• Now the de facto standard for decoder-only models
THE PROBLEM WITH ABSOLUTE POSITIONS
───────────────────────────────────
Original transformer: add position embedding to token embedding
x_i = token_embed(i) + pos_embed(i)
Problems:
• Model sees absolute positions, not relative distances
• Fixed maximum length (e.g., 512 or 2048)
• Poor generalization to longer sequences
THE ROPE INSIGHT
────────────────
Key idea: Encode position through ROTATION in 2D subspaces.
For position m, rotate the embedding by angle m*θ:
┌ ┐ ┌ ┐ ┌ ┐
│ q'{2i} │ │ cos(mθ_i) -sin(mθ_i)│ │q{2i}│
│ │ = │ │ │ │
│q'{2i+1}│ │ sin(mθ_i) cos(mθ_i)│ │q{2i+1}│
└ ┘ └ ┘ └ ┘
Where θ_i = 10000^(-2i/d) (similar to sinusoidal encoding)
WHY ROTATION WORKS
──────────────────
When computing attention between positions m and n:
q_m · k_n = (R_m * q) · (R_n * k)
= q · (R_m^T * R_n) · k
= q · R_{n-m} · k (rotation matrices compose!)
The dot product only depends on RELATIVE position (n-m), not absolute positions!
IMPLEMENTATION
──────────────
In practice, implemented as element-wise operations (more efficient):
def apply_rope(x, cos, sin):
# x shape: (batch, seq_len, n_heads, head_dim)
# Split into pairs
x1 = x[..., ::2] # even indices
x2 = x[..., ::2] # odd indices
# Rotate
rotated = torch.cat([
x1 * cos - x2 * sin,
x1 * sin + x2 * cos
], dim=-1)
return rotated
# Precompute frequencies
def precompute_freqs(dim, max_seq_len, base=10000):
freqs = 1.0 / (base ** (torch.arange(0, dim, 2) / dim))
t = torch.arange(max_seq_len)
freqs = torch.outer(t, freqs) # (seq_len, dim/2)
cos = freqs.cos()
sin = freqs.sin()
return cos, sin
COMPARISON: POSITION ENCODING METHODS
─────────────────────────────────────
┌─────────────────────────────────────────────────────────────────────────────┐
│ Method Relative? Extrapolate? Used In │
├─────────────────────────────────────────────────────────────────────────────┤
│ Absolute Learned No No GPT-2, BERT │
│ Sinusoidal No Somewhat Original Transformer │
│ ALiBi Yes Yes BLOOM, MPT │
│ RoPE Yes Yes* Llama, Mistral, Qwen │
│ T5 Relative Bias Yes No T5, Flan │
└─────────────────────────────────────────────────────────────────────────────┘
- With appropriate scaling techniques
EXTENDING CONTEXT LENGTH WITH ROPE
──────────────────────────────────
RoPE enables various context extension techniques:
-
Position Interpolation (PI):
- Scale positions: pos' = pos * (L_train / L_target)
- Example: trained on 4K, extend to 16K by scaling by 0.25
- Requires some fine-tuning
-
NTK-aware Scaling:
- Modify the base frequency: base' = base * α^(dim/(dim-2))
- Better preserves high-frequency information
-
YaRN (Yet another RoPE extensioN):
- Combines interpolation with NTK scaling
- Different scaling for different frequency bands
- State-of-the-art for context extension
-
Dynamic NTK:
- Adjust scaling based on actual sequence length
- No fine-tuning needed (but less optimal)
KEY PAPER
─────────
• "RoFormer: Enhanced Transformer with Rotary Position Embedding" (2021)
################################################################################
4. GQA - GROUPED QUERY ATTENTION
################################################################################
WHAT IS IT?
───────────
GQA is an attention variant where multiple query heads share the same
key-value heads. It's a middle ground between MHA and MQA.
WHY IT MATTERS
──────────────
• KV cache is the memory bottleneck for long sequences
• GQA reduces KV cache size significantly
• Minimal quality loss compared to full MHA
• Used in Llama 2 70B, Mistral, and most modern large models
• Enables longer contexts and larger batch sizes
THE EVOLUTION
─────────────
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ MHA (Multi-Head Attention) - Original Transformer │
│ ───────────────────────────────────────────────── │
│ • Each head has its own Q, K, V projections │
│ • n_heads queries, n_heads keys, n_heads values │
│ • Full expressiveness, high memory │
│ │
│ Q heads: [Q1] [Q2] [Q3] [Q4] [Q5] [Q6] [Q7] [Q8] │
│ K heads: [K1] [K2] [K3] [K4] [K5] [K6] [K7] [K8] │
│ V heads: [V1] [V2] [V3] [V4] [V5] [V6] [V7] [V8] │
│ │
│ MQA (Multi-Query Attention) - Google 2019 │
│ ──────────────────────────────────────────── │
│ • All query heads share ONE key-value head │
│ • n_heads queries, 1 key, 1 value │
│ • Massive memory savings, some quality loss │
│ │
│ Q heads: [Q1] [Q2] [Q3] [Q4] [Q5] [Q6] [Q7] [Q8] │
│ K heads: [─────────────── K1 ───────────────────] │
│ V heads: [─────────────── V1 ───────────────────] │
│ │
│ GQA (Grouped Query Attention) - Google 2023 │
│ ─────────────────────────────────────────────── │
│ • Query heads grouped, each group shares K-V │
│ • n_heads queries, n_kv_heads keys, n_kv_heads values │
│ • Balance of quality and efficiency │
│ │
│ Q heads: [Q1] [Q2] [Q3] [Q4] | [Q5] [Q6] [Q7] [Q8] │
│ K heads: [───── K1 ─────────] [───── K2 ─────────] │
│ V heads: [───── V1 ─────────] [───── V2 ─────────] │
│ └── group 1 ───────┘ └─── group 2 ──────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
MEMORY ANALYSIS
───────────────
KV cache size per token = 2 * n_layers * n_kv_heads * head_dim * bytes
Example: Llama 2 70B
• 80 layers, 64 query heads, 8 KV heads (GQA with 8 groups)
• head_dim = 128
• KV cache per token = 2 * 80 * 8 * 128 * 2 bytes = 328 KB
If it were MHA (64 KV heads):
• KV cache per token = 2 * 80 * 64 * 128 * 2 bytes = 2.6 MB
That's 8x memory savings! For a 4096 context, that's 10GB vs 1.3GB.
IMPLEMENTATION
──────────────
class GroupedQueryAttention(nn.Module):
def __init__(self, d_model, n_heads, n_kv_heads):
super().__init__()
self.n_heads = n_heads
self.n_kv_heads = n_kv_heads
self.n_groups = n_heads // n_kv_heads # queries per KV head
self.head_dim = d_model // n_heads
self.q_proj = nn.Linear(d_model, n_heads * self.head_dim)
self.k_proj = nn.Linear(d_model, n_kv_heads * self.head_dim)
self.v_proj = nn.Linear(d_model, n_kv_heads * self.head_dim)
self.o_proj = nn.Linear(n_heads * self.head_dim, d_model)
def forward(self, x):
B, L, _ = x.shape
q = self.q_proj(x).view(B, L, self.n_heads, self.head_dim)
k = self.k_proj(x).view(B, L, self.n_kv_heads, self.head_dim)
v = self.v_proj(x).view(B, L, self.n_kv_heads, self.head_dim)
# Expand K, V to match Q heads (repeat for each group)
k = k.repeat_interleave(self.n_groups, dim=2)
v = v.repeat_interleave(self.n_groups, dim=2)
# Standard attention from here
# ...
COMMON CONFIGURATIONS
─────────────────────
┌─────────────────────────────────────────────────────────────────────────────┐
│ Model n_heads n_kv_heads Ratio Type │
├─────────────────────────────────────────────────────────────────────────────┤
│ GPT-2 12 12 1:1 MHA │
│ Llama 1 32 32 1:1 MHA │
│ Llama 2 7B 32 32 1:1 MHA │
│ Llama 2 70B 64 8 8:1 GQA │
│ Mistral 7B 32 8 4:1 GQA │
│ Falcon 40B 64 1 64:1 MQA │
└─────────────────────────────────────────────────────────────────────────────┘
CONVERTING MHA TO GQA
─────────────────────
You can convert existing MHA models to GQA:
- Group the KV heads
- Average or select representative heads within each group
- Fine-tune briefly to recover quality
KEY PAPER
─────────
• "GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints" (2023)
################################################################################
5. KV CACHE
################################################################################
WHAT IS IT?
───────────
KV Cache stores the key and value tensors from previous tokens during
autoregressive generation, avoiding redundant computation.
WHY IT MATTERS
──────────────
• Without KV cache: O(n²) computation per token (recompute all attention)
• With KV cache: O(n) computation per token
• Essential for practical inference speeds
• Main memory bottleneck for long sequences
• Understanding it is key to optimizing inference
THE PROBLEM
───────────
Autoregressive generation: predict one token at a time
"The cat" → "sat" → "on" → "the" → "mat"
Naive approach: For each new token, recompute attention over ALL previous tokens
Step 1: Attend over [The] - 1 token
Step 2: Attend over [The, cat] - 2 tokens
Step 3: Attend over [The, cat, sat] - 3 tokens
...
Step n: Attend over all n tokens - n tokens
Total attention computations: 1 + 2 + 3 + ... + n = O(n²)
THE SOLUTION: KV CACHE
──────────────────────
Key insight: K and V for previous tokens DON'T CHANGE!
Attention(Q, K, V) = softmax(QK^T / √d) V
For token at position i:
• Q_i depends only on token i (the new token)
• K_j, V_j for j < i were computed in previous steps
So: Cache all K and V, only compute new K_i, V_i, Q_i
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ Step 1: Input "The" │
│ ────────────────── │
│ Compute: Q₁, K₁, V₁ │
│ Cache: K_cache = [K₁], V_cache = [V₁] │
│ Attend: Q₁ @ [K₁]ᵀ │
│ │
│ Step 2: Input "cat" │
│ ─────────────────── │
│ Compute: Q₂, K₂, V₂ (only for new token!) │
│ Cache: K_cache = [K₁, K₂], V_cache = [V₁, V₂] │
│ Attend: Q₂ @ [K₁, K₂]ᵀ │
│ │
│ Step 3: Input "sat" │
│ ─────────────────── │
│ Compute: Q₃, K₃, V₃ (only for new token!) │
│ Cache: K_cache = [K₁, K₂, K₃], V_cache = [V₁, V₂, V₃] │
│ Attend: Q₃ @ [K₁, K₂, K₃]ᵀ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Each step only computes 1 new Q, K, V instead of all previous ones!
MEMORY REQUIREMENTS
───────────────────
KV cache size = 2 * n_layers * seq_len * n_kv_heads * head_dim * bytes
Example: Llama 2 7B, 4096 context, FP16
• 32 layers, 32 KV heads, head_dim = 128
• Size = 2 * 32 * 4096 * 32 * 128 * 2 = 2.1 GB
This is why KV cache is often the bottleneck, not model weights!
IMPLEMENTATION SKETCH
─────────────────────
class CachedAttention(nn.Module):
def forward(self, x, kv_cache=None, use_cache=True):
B, L, D = x.shape
q = self.q_proj(x)
k = self.k_proj(x)
v = self.v_proj(x)
if kv_cache is not None:
# Concatenate with cached K, V
k_cache, v_cache = kv_cache
k = torch.cat([k_cache, k], dim=1)
v = torch.cat([v_cache, v], dim=1)
# Standard attention computation
attn_out = scaled_dot_product_attention(q, k, v)
if use_cache:
new_cache = (k, v)
return attn_out, new_cache
return attn_out, None
ADVANCED KV CACHE TECHNIQUES
────────────────────────────
-
PagedAttention (vLLM):
• Manage KV cache like virtual memory pages
• Non-contiguous allocation reduces fragmentation
• Enables efficient batching of variable-length sequences -
Sliding Window Cache:
• Only keep last W tokens in cache
• Used in Mistral (W=4096)
• O(W) memory instead of O(n) -
KV Cache Quantization:
• Store cached K, V in lower precision (INT8, INT4)
• Reduces memory by 2-4x
• Minimal quality impact -
KV Cache Compression:
• Learned compression of cached states
• Trade compute for memory -
Speculative Decoding:
• Use smaller model to draft multiple tokens
• Verify with large model in parallel
• Amortizes KV cache updates
PREFILL VS DECODE
─────────────────
Two distinct phases in generation:
Prefill (prompt processing):
• Process entire prompt at once
• Compute-bound (matrix multiplications)
• KV cache is populated
Decode (token generation):
• Generate one token at a time
• Memory-bound (KV cache reads)
• Much slower per-token than prefill
This is why "time to first token" differs from "tokens per second"!
################################################################################
6. LoRA - LOW-RANK ADAPTATION
################################################################################
WHAT IS IT?
───────────
LoRA fine-tunes LLMs by adding small trainable low-rank matrices to frozen
pretrained weights. It's the most popular parameter-efficient fine-tuning method.
WHY IT MATTERS
──────────────
• Full fine-tuning requires storing full model gradients (huge memory)
• LoRA reduces trainable parameters by 10,000x
• Can fine-tune 65B models on consumer GPUs
• Multiple LoRA adapters can be hot-swapped
• Quality comparable to full fine-tuning
THE CORE IDEA
─────────────
Weight updates during fine-tuning are low-rank!
Instead of updating full weight W:
W' = W + ΔW
Decompose ΔW into low-rank matrices:
ΔW = BA where B ∈ R^(d×r), A ∈ R^(r×k), r << min(d,k)
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ Full Fine-tuning: LoRA: │
│ ───────────────── ───── │
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ │ │ │ │
│ │ W + ΔW │ │ W (frozen) │───┐ │
│ │ (d × k) │ │ (d × k) │ │ │
│ │ │ │ │ │ │
│ └───────────────┘ └───────────────┘ │ ┌─────┐ │
│ ├──│ + │──▶ │
│ Trainable: d × k ┌─────┐ │ └─────┘ │
│ (millions of params) │ B │─────────────┘ │
│ │(d×r)│ │
│ └──┬──┘ │
│ │ │
│ ┌──┴──┐ │
│ │ A │ │
│ │(r×k)│ ◀── trainable │
│ └─────┘ │
│ │
│ Trainable: r×(d+k) │
│ (thousands of params) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
PARAMETER SAVINGS
─────────────────
Original matrix: d × k parameters
LoRA: r × d + r × k = r(d + k) parameters
Example: d=4096, k=4096, r=8
• Full: 16.7M parameters
• LoRA: 65K parameters (256x reduction!)
WHICH LAYERS TO ADAPT?
──────────────────────
Typically applied to attention projection matrices:
• Q projection (most common)
• V projection (most common)
• K projection (sometimes)
• O projection (sometimes)
• FFN layers (less common, but can help)
Common configs:
• Attention only: q_proj, v_proj
• All attention: q_proj, k_proj, v_proj, o_proj
• Full: + FFN layers
IMPLEMENTATION
──────────────
class LoRALinear(nn.Module):
def __init__(self, original_layer, r=8, alpha=16):
super().__init__()
self.original = original_layer
self.original.weight.requires_grad = False # Freeze
d, k = original_layer.weight.shape
self.lora_A = nn.Parameter(torch.randn(r, k) * 0.01)
self.lora_B = nn.Parameter(torch.zeros(d, r))
self.scaling = alpha / r # Scale factor
def forward(self, x):
# Original path (frozen)
original_out = self.original(x)
# LoRA path (trainable)
lora_out = (x @ self.lora_A.T @ self.lora_B.T) * self.scaling
return original_out + lora_out
KEY HYPERPARAMETERS
───────────────────
• r (rank): 4, 8, 16, 32 typical. Higher = more capacity, more params
• alpha: Scaling factor, typically 16 or 2*r. Affects learning rate effectively
• Target modules: Which layers to apply LoRA to
• Learning rate: Usually higher than full fine-tuning (1e-4 to 3e-4)
MERGING LORA
────────────
After training, LoRA can be merged into base weights (no inference overhead):
W_merged = W + BA * (alpha/r)
This means:
• Training: Keep LoRA separate
• Deployment: Merge for zero overhead OR keep separate for hot-swapping
QLORA: COMBINING WITH QUANTIZATION
──────────────────────────────────
QLoRA = 4-bit quantized base + LoRA adapters
• Base model in 4-bit NormalFloat (NF4)
• LoRA adapters in FP16/BF16
• Double quantization for further savings
• Fine-tune 65B models on single 48GB GPU!
VARIANTS
────────
• LoRA+: Different learning rates for A and B
• DoRA: Decomposes into magnitude and direction
• AdaLoRA: Adaptive rank allocation
• QLoRA: Quantized base model
• LongLoRA: Efficient long-context fine-tuning
KEY PAPERS
──────────
• "LoRA: Low-Rank Adaptation of Large Language Models" (2021)
• "QLoRA: Efficient Finetuning of Quantized LLMs" (2023)
################################################################################
7. QUANTIZATION
################################################################################
WHAT IS IT?
───────────
Quantization reduces model precision from FP32/FP16 to INT8/INT4, making
models smaller and faster while maintaining acceptable quality.
WHY IT MATTERS
──────────────
• 70B parameter model in FP16 = 140GB (won't fit on most hardware)
• Same model in INT4 = 35GB (fits on high-end consumer GPU)
• 2-4x speedup from reduced memory bandwidth
• Enables local deployment of large models
• Critical for edge/mobile deployment
PRECISION FORMATS
─────────────────
┌─────────────────────────────────────────────────────────────────────────────┐
│ Format Bits Range Use Case │
├─────────────────────────────────────────────────────────────────────────────┤
│ FP32 32 ±3.4×10³⁸ Training (legacy) │
│ FP16 16 ±65504 Training/Inference │
│ BF16 16 ±3.4×10³⁸ Training (better range than FP16) │
│ INT8 8 -128 to 127 Inference │
│ INT4 4 -8 to 7 Inference (aggressive) │
│ NF4 4 Normal dist QLoRA (better for weights) │
│ FP8 8 Varies Emerging for training │
└─────────────────────────────────────────────────────────────────────────────┘
BASIC QUANTIZATION MATH
───────────────────────
Convert floating point to integer:
x_quant = round(x / scale) + zero_point
x_dequant = (x_quant - zero_point) * scale
Where:
scale = (max_val - min_val) / (2^bits - 1)
zero_point = round(-min_val / scale)
QUANTIZATION GRANULARITY
────────────────────────
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ Per-tensor: One scale for entire tensor │
│ ──────────────────────────────────────── │
│ ┌─────────────────────────┐ │
│ │ scale = 0.5 │ Simple but less accurate │
│ │ ░░░░░░░░░░░░░░░░░░░░░ │ │
│ └─────────────────────────┘ │
│ │
│ Per-channel: One scale per output channel │
│ ───────────────────────────────────────── │
│ ┌─────────────────────────┐ │
│ │ scale = [0.5, 0.3, 0.7]│ Better accuracy, standard for weights │
│ │ ░░░░░░░░░░░░░░░░░░░░░ │ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │
│ │ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │ │
│ └─────────────────────────┘ │
│ │
│ Per-group: One scale per group of values (e.g., 128 values) │
│ ─────────────────────────────────────────────────────────── │
│ ┌─────────────────────────┐ │
│ │ [s1][s2][s3][s4]... │ Best accuracy for INT4, used in GPTQ/AWQ │
│ │ ░░░░ ▓▓▓▓ ▒▒▒▒ ████ │ │
│ └─────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
QUANTIZATION METHODS
────────────────────
-
POST-TRAINING QUANTIZATION (PTQ)
Quantize after training, no retraining neededa) Naive Round-to-Nearest (RTN):
• Just round weights to nearest quantized value
• Fast but lower qualityb) GPTQ (GPT Quantization):
• Uses calibration data to minimize quantization error
• Quantizes weights one at a time, adjusting remaining weights
• Based on Optimal Brain Quantization
• Very popular for INT4c) AWQ (Activation-aware Weight Quantization):
• Identifies important weights based on activation magnitudes
• Keeps important weights at higher precision
• Often better than GPTQ -
QUANTIZATION-AWARE TRAINING (QAT)
Simulate quantization during training• Forward: Use quantized weights
• Backward: Use straight-through estimator (STE)
• Better quality but requires training
WEIGHT VS ACTIVATION QUANTIZATION
─────────────────────────────────
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ Weights (easy): Activations (hard): │
│ • Static, known at compile time • Dynamic, vary per input │
│ • Gaussian-ish distribution • Can have outliers │
│ • INT4 works well • Usually need INT8 or higher │
│ │
│ Common setups: │
│ • W8A8: INT8 weights, INT8 activations (balanced) │
│ • W4A16: INT4 weights, FP16 activations (popular for inference) │
│ • W4A8: INT4 weights, INT8 activations (aggressive) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
THE OUTLIER PROBLEM
───────────────────
LLMs have outlier features (very large activation values in few dimensions).
These break naive quantization.
Solutions:
• LLM.int8(): Mixed precision for outlier channels
• SmoothQuant: Migrate quantization difficulty from activations to weights
• GPTQ/AWQ: Handle outliers through calibration
PRACTICAL QUANTIZATION TOOLS
────────────────────────────
• bitsandbytes: Easy INT8/INT4 with LLM.int8(), used by QLoRA
• AutoGPTQ: GPTQ implementation
• AutoAWQ: AWQ implementation
• llama.cpp: GGML/GGUF formats, CPU-optimized
• TensorRT-LLM: NVIDIA's optimized inference
QUALITY vs SIZE TRADEOFF
────────────────────────
┌─────────────────────────────────────────────────────────────────────────────┐
│ Method Size Reduction Quality Loss Notes │
├─────────────────────────────────────────────────────────────────────────────┤
│ FP16→INT8 2x ~0-1% Very safe │
│ FP16→INT4 4x ~1-3% Good with GPTQ/AWQ │
│ FP16→INT3 5.3x ~3-5% Noticeable degradation │
│ FP16→INT2 8x ~10%+ Significant loss │
└─────────────────────────────────────────────────────────────────────────────┘
Quality loss depends heavily on model size (larger models quantize better).
KEY PAPERS
──────────
• "LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale" (2022)
• "GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers" (2022)
• "AWQ: Activation-aware Weight Quantization for LLM Compression" (2023)
################################################################################
8. RAG - RETRIEVAL AUGMENTED GENERATION
################################################################################
WHAT IS IT?
───────────
RAG enhances LLMs by retrieving relevant documents from an external knowledge
base and including them in the prompt context.
WHY IT MATTERS
──────────────
• LLMs have fixed knowledge (training cutoff date)
• Fine-tuning for every knowledge update is expensive
• RAG provides "infinite" updatable knowledge
• Reduces hallucinations by grounding in sources
• Enables domain-specific applications
• Core technique for most production LLM applications
THE RAG PIPELINE
────────────────
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ INDEXING (Offline) │
│ ────────────────── │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Documents │───▶│ Chunk │───▶│ Embed │───▶│ Vector │ │
│ │ │ │ │ │ │ │ DB │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ RETRIEVAL + GENERATION (Online) │
│ ─────────────────────────────── │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Query │───▶│ Embed │───▶│ Retrieve │───▶│ Rerank │ │
│ │ │ │ Query │ │ Top-K │ │(optional)│ │
│ └──────────┘ └──────────┘ └──────────┘ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ ┌──────────────────────────────────────────┐ │
│ │ Response │◀───│ LLM: "Given context: {docs}, answer: {q}"│ │
│ │ │ │ │ │
│ └──────────┘ └──────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
COMPONENT DEEP DIVE
───────────────────
-
CHUNKING
Split documents into smaller pieces for embeddingStrategies:
• Fixed size: Every N tokens (simple, can break mid-sentence)
• Recursive: Split by paragraphs, then sentences, then words
• Semantic: Split at topic boundaries
• Sentence-based: Keep sentence integrityTypical chunk size: 256-512 tokens
Overlap: 10-20% (prevents losing context at boundaries) -
EMBEDDING
Convert text to dense vectors for similarity searchPopular models:
• OpenAI text-embedding-ada-002/3-small/3-large
• Cohere embed-v3
• BGE (open source, competitive)
• E5 (open source)
• GTE (open source)Dimension: 384-3072 (higher = more capacity, more compute)
-
VECTOR DATABASE
Store and search embeddings efficientlyOptions:
• Pinecone (managed, scalable)
• Weaviate (open source, feature-rich)
• Milvus (open source, scalable)
• Chroma (open source, simple)
• Qdrant (open source, fast)
• pgvector (PostgreSQL extension)
• FAISS (library, not a DB) -
RETRIEVAL
Find relevant chunks for a queryMethods:
• Dense: Embedding similarity (cosine, dot product)
• Sparse: BM25, TF-IDF (keyword matching)
• Hybrid: Combine dense + sparse (often best)Typical: Retrieve top 3-10 chunks
-
RERANKING (Optional but recommended)
Re-score retrieved chunks with a more powerful model• Cross-encoders score (query, document) pairs
• Much more accurate than bi-encoders
• But too slow for initial retrieval
• Examples: Cohere Rerank, BGE Reranker
PROMPT CONSTRUCTION
───────────────────
System: You are a helpful assistant. Answer based on the provided context.
If the answer is not in the context, say "I don't know."
Context:
[Retrieved chunk 1]
[Retrieved chunk 2]
[Retrieved chunk 3]
User: {user_question}
ADVANCED RAG TECHNIQUES
───────────────────────
-
Query Transformation:
• Query expansion: Add synonyms, related terms
• HyDE: Generate hypothetical answer, embed that instead
• Multi-query: Generate multiple query variations -
Hierarchical Retrieval:
• Summary-level → Document-level → Chunk-level
• Retrieve parents of matched chunks for more context -
Self-RAG:
• Model decides when to retrieve
• Model evaluates retrieval quality
• Model generates citations -
Agentic RAG:
• Multi-step retrieval with reasoning
• Query planning and decomposition
• Tool use for structured data -
Graph RAG:
• Build knowledge graph from documents
• Retrieve via graph traversal
• Better for multi-hop questions
EVALUATION METRICS
──────────────────
Retrieval:
• Recall@K: % of relevant docs in top K
• MRR: Mean Reciprocal Rank
• NDCG: Normalized Discounted Cumulative Gain
Generation:
• Faithfulness: Does answer match retrieved context?
• Relevance: Does answer address the question?
• RAGAS: Popular RAG evaluation framework
COMMON PITFALLS
───────────────
• Chunk size too large: Dilutes relevance signal
• Chunk size too small: Loses context
• No reranking: Embedding similarity != relevance
• Ignoring metadata: Dates, sources help filtering
• No citation: Users can't verify answers
KEY PAPERS
──────────
• "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks" (2020)
• "Self-RAG: Learning to Retrieve, Generate, and Critique" (2023)
################################################################################
9. CHAIN-OF-THOUGHT (CoT)
################################################################################
WHAT IS IT?
───────────
Chain-of-Thought prompting encourages LLMs to generate intermediate reasoning
steps before giving a final answer, dramatically improving performance on
complex tasks.
WHY IT MATTERS
──────────────
• Enables reasoning capabilities not seen in base models
• Can turn a failing model into a succeeding one
• Simple to implement (just add "Let's think step by step")
• Foundational for more advanced reasoning techniques
• Key capability difference between GPT-3 and ChatGPT-era models
THE CORE INSIGHT
────────────────
LLMs struggle with multi-step reasoning when asked directly:
Q: "If John has 3 apples and buys 2 more, then gives half to Mary,
how many does John have?"
Without CoT: "2" (wrong, guessing)
With CoT: "John starts with 3 apples. He buys 2 more, so 3+2=5.
He gives half to Mary, so 5/2=2.5. John has 2.5 apples." ✓
Generating intermediate steps:
- Breaks complex problems into simpler sub-problems
- Provides "working memory" in the context
- Allows error detection and correction
- Matches how humans solve problems
COT PROMPTING METHODS
─────────────────────
-
ZERO-SHOT COT
Just add "Let's think step by step"┌─────────────────────────────────────────────────────────────────────────┐
│ Q: [Complex question] │
│ │
│ A: Let's think step by step. │
│ [Model generates reasoning...] │
│ Therefore, the answer is [X]. │
└─────────────────────────────────────────────────────────────────────────┘Effectiveness varies by model. Works well on GPT-4, Claude, etc.
-
FEW-SHOT COT
Provide examples with reasoning chains┌─────────────────────────────────────────────────────────────────────────┐
│ Q: Roger has 5 tennis balls. He buys 2 more cans of balls. Each can │
│ has 3 balls. How many tennis balls does he have now? │
│ A: Roger started with 5 balls. He bought 2 cans of 3 balls each, │
│ so 2 * 3 = 6 balls. 5 + 6 = 11. The answer is 11. │
│ │
│ Q: [New question] │
│ A: │
└─────────────────────────────────────────────────────────────────────────┘ -
SELF-CONSISTENCY
Generate multiple CoT paths, take majority vote┌─────────────────────────────────────────────────────────────────────────┐
│ Question │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ CoT Path 1 CoT Path 2 CoT Path 3 │
│ Answer: A Answer: B Answer: A │
│ │ │ │ │
│ └─────────────┼─────────────┘ │
│ ▼ │
│ Majority Vote: A │
└─────────────────────────────────────────────────────────────────────────┘Use temperature > 0 for diverse paths. Significantly improves accuracy.
-
TREE OF THOUGHTS (ToT)
Explore multiple reasoning branches, backtrack if needed┌─────────────────────────────────────────────────────────────────────────┐
│ Problem │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ Thought 1 Thought 2 Thought 3 │
│ (score: 8) (score: 3) (score: 7) │
│ │ ✗ │ │
│ ┌────┴────┐ ┌───────┴────┐ │
│ ▼ ▼ ▼ ▼ │
│ T1.1 T1.2 T3.1 T3.2 │
│ (score:9) (score:4) (score:6) (score:8) │
│ │ ✗ ✗ │ │
│ ▼ ▼ │
│ Solution 1 Solution 2 │
└─────────────────────────────────────────────────────────────────────────┘Uses LLM to evaluate promising paths. Good for planning problems.
ADVANCED REASONING TECHNIQUES
─────────────────────────────
-
Least-to-Most Prompting:
• Decompose complex problem into sub-problems
• Solve sub-problems in order
• Use previous answers for later sub-problems -
Program-Aided Language (PAL):
• Generate code instead of natural language reasoning
• Execute code to get answer
• More reliable for math/logic -
ReAct (Reasoning + Acting):
• Interleave reasoning with tool use
• Thought → Action → Observation → Thought...
• Foundation for agents -
Reflection/Self-Critique:
• Generate answer
• Critique the answer
• Revise based on critique
• Repeat
WHEN COT HELPS
──────────────
✓ Math word problems
✓ Multi-step logic
✓ Commonsense reasoning
✓ Code generation
✓ Planning tasks
WHEN COT DOESN'T HELP
─────────────────────
✗ Simple factual recall
✗ Tasks where direct answer is obvious
✗ Very small models (need sufficient capability)
✗ When speed matters (generates more tokens)
COT IN TRAINING
───────────────
Modern models are trained with CoT:
• Include reasoning traces in training data
• Reward models score reasoning quality, not just final answer
• "Process supervision" vs "outcome supervision"
This is why ChatGPT-era models are better at reasoning than GPT-3.
IMPLEMENTATION TIP
──────────────────
For production systems, often use:
- System prompt: "Think through problems step by step before answering."
- Structured output: Have model output {"reasoning": "...", "answer": "..."}
- Hide reasoning: Show user only the answer (CoT happens internally)
KEY PAPERS
──────────
• "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models" (2022)
• "Self-Consistency Improves Chain of Thought Reasoning" (2022)
• "Tree of Thoughts: Deliberate Problem Solving with LLMs" (2023)
• "ReAct: Synergizing Reasoning and Acting in Language Models" (2022)
################################################################################
SUMMARY: YOUR LEARNING PATH
################################################################################
You've now covered the theory for all Tier 1 topics. Here's a suggested
practical progression:
COMPLETED (from nanoGPT + Dolly):
✓ Transformer fundamentals
✓ SFT (Supervised Fine-Tuning)
✓ BPE Tokenization
RECOMMENDED NEXT IMPLEMENTATIONS:
-
LoRA Fine-tuning
• Use HuggingFace PEFT library
• Fine-tune Llama 7B on your dataset
• Experiment with rank, alpha, target modules -
DPO Alignment
• Use TRL library
• Create preference dataset from your SFT model outputs
• Train DPO on top of your SFT model -
RAG System
• Use LangChain or LlamaIndex
• Build document Q&A over your own documents
• Experiment with chunking, embedding models, reranking -
Quantization
• Quantize your fine-tuned model with AutoGPTQ or bitsandbytes
• Compare inference speed and quality
• Try different bit widths (8, 4) -
Architecture Study
• Read Llama source code
• Implement RoPE from scratch
• Implement GQA attention
RESOURCES:
• HuggingFace Transformers: https://github.com/huggingface/transformers
• TRL (Training with RL): https://github.com/huggingface/trl
• PEFT (LoRA etc): https://github.com/huggingface/peft
• LangChain: https://github.com/langchain-ai/langchain
• vLLM: https://github.com/vllm-project/vllm