Contents50 sections
Chapter Overview:
Welcome to the magnificent, beating heart of modern large language models—the Transformer Neural Architecture.
Many beginners learning Transformers find themselves trapped between two extremes: either staring at cold lines of PyTorch code without understanding the underlying intuition, or drowning in dense academic mathematics without knowing how it translates to GPU memory operations.
This chapter bridges engineering intuition with rigorous mathematics:
We demystify the model as a "12-story intelligent skyscraper hosting round-table debates," using relatable real-world analogies to establish clear intuition;
At critical junctures, we provide solid, uncompromising mathematical derivations (from Euler's complex plane rotation for RoPE to independent identically distributed variance scaling proofs and autoregressive probability chains), piercing through surface jargon to understand every tensor operator;
💡 Recommended Mathematical Foundations Deep Dive:
If you wish to trace every derivation from high-school mathematics (plane vectors, trigonometric identities, imaginary units , polar coordinates, and variance statistics) on scratch paper, consult our dedicated supplementary guide: Modern Transformer Mathematical Derivations & First Principles.
2.1 Architectural Bird's-Eye View: The Odyssey of Information in a 12-Story Intelligent Skyscraper
Before writing code, let us take a macro bird's-eye view of how a batch of text journeys through the entire model.
2.1.1 What Is a "Hidden Vector"? The 512-Dimensional Agent Profile Dossier
In the previous chapter, our Tokenizer converted raw human text into discrete numeric IDs (e.g., "从" 232). However, discrete integers cannot capture semantic richness; a neural network cannot compute dot products on solitary integers.
To help computers grasp contextual semantics, the first step is passing tokens through an Embedding Layer, assigning each token a 512-dimensional continuous floating-point vector (d_model = 512).
- Intuitive Metaphor: Picture these 512 floating-point numbers as a "comprehensive intelligence dossier profiling a character":
- Dimension 1 might represent "noun inclination vs. verb inclination";
- Dimension 2 might represent "emotional tone: joyful vs. melancholic";
- Dimension 3 might represent "domain: natural scenery, technology, or domestic life";
- ……Together, all 512 fine-grained calibrated coordinates pinpoint this word's unique semantic location in high-dimensional space!
2.1.2 The 12-Layer Stack: Continuous Semantic Elevation and Round-Table Deliberation
Once all words in a sentence are converted into a sequence of profile dossiers, they enter this modern 12-story Transformer skyscraper:
- Lower Floors (Layers 1 ~ 3): Focus on fundamental syntax, parts of speech, and adjacent phrase collocations (e.g., recognizing that "artificial intelligence" forms a coherent unit);
- Middle Floors (Layers 4 ~ 8): Begin parsing sentence syntax, hierarchical subordination, and coreference resolution (e.g., clarifying whether "it" refers to the cat or the dog);
- Upper Floors (Layers 9 ~ 12): Perform high-level logical reasoning, commonsense association, emotional intention parsing, and world-knowledge extraction, setting the stage for predicting the next token!
===================================================================================
0.04B Mini-LLaMA 数据穿梭全景时序图
===================================================================================
[ 输入 Token 序列 ]: 形状 [Batch=2, Seq_Len=1024]
│
▼ 1. 词嵌入映射 Embedding(4096 -> 512)
[ 连续隐向量矩阵 ]: 形状 [Batch=2, Seq_Len=1024, Dim=512]
│
▼ 2. 依次穿过 12 层高度一致的 Transformer Block 研讨层
│
│ ┌── 单层 Transformer Block 内部两阶段循环 ───────────────────┐
│ │ │
│ │ 【第一阶段: 注意力交换情报 (Attention)】 │
│ │ 输入向量 X ──┬─────────────────────────────[残差直连 +]──┐│
│ │ ▼ ││
│ │ [ RMSNorm 音量平衡 ] ││
│ │ ▼ ││
│ │ [ GQA 分组查询注意力 (8Q:2KV) + RoPE 旋转位置编码 ]│
│ │ ▼ ││
│ │ [ W_o 总结汇报投影 ] ────────────────────────────┘│
│ │ │ │
│ │ ▼ 产生中间特征向量 H │
│ │ 【第二阶段: 翻书与独立思考 (FeedForward)】 │
│ │ 中间向量 H ──┬─────────────────────────────[残差直连 +]──┐│
│ │ ▼ ││
│ │ [ RMSNorm 音量平衡 ] ││
│ │ ▼ ││
│ │ [ SwiGLU 双通道门控前馈网络 (中间维度 1408) ] ││
│ │ ▼ ││
│ │ [ W_down 降维输出 ] ─────────────────────────────┘│
│ │ │ │
│ │ ▼ 输出提炼后的新向量: 形状依然保持 [2, 1024, 512]
│ └───────────────────────────────────────────────────────────┘
│
│ (顺次历经 12 层深层研讨加工)
▼
[ 终层 Final RMSNorm ]: 稳定全网输出幅度
│
▼ 3. LM Head 预测输出投影 (与 Embedding 共享权重矩阵)
[ 未归一化打分 Logits ]: 形状 [Batch=2, Seq_Len=1024, Vocab=4096]
│
▼ 4. 交叉熵计算损失 (训练) 或 采样下一个词 (推理)
===================================================================================2.1.3 Fundamental Rule: Constant Backbone Tensor Shape
Notice the tensor dimensions flowing across the architecture: regardless of whether signals traverse Layer 1 or Layer 12, the backbone tensor shape remains strictly constant: [Batch, Seq_Len, 512]! Information is enriched and refined continuously, but its dimensional format stays uniform throughout.
2.1.4 The Four Key Architectural Evolutions of Modern Transformers
| Core Architectural Module | Legacy Transformer (2017) | Modern Open-Weight Frontier (LLaMA-3 / Qwen-2.5) | MiniLLaMA in This Tutorial | | Normalization Scheme | Post-LayerNorm (computes mean and variance; prone to vanishing gradients) | Pre-RMSNorm (drops mean calculation; stabilizes deep gradient highways) | Pre-RMSNorm | | Positional Encoding | Absolute Positional Embeddings (fixed sinusoidal or learned lookup tables) | Rotary Position Embedding (RoPE, complex plane relative angle rotation) | RoPE (Rotary Position Embeddings) | | Feed-Forward Network (FFN) | Standard single-channel ReLU / GELU FFN | Dual-channel gated SwiGLU architecture (valve aperture multiplied by features) | SwiGLU (Dim=1408) | | Self-Attention Mechanism | Multi-Head Attention (1 Query-to-KV ratio; high VRAM footprint) | Grouped-Query Attention (GQA, multiple Q heads share KV heads) | GQA (8 Query : 2 KV Heads) |
Now, let us examine each of these four core building blocks, derive their mathematical mechanics, and implement them in PyTorch!
2.2 Building Block 1: RMSNorm (Automatic Volume Balancer)
2.2.1 Why Must Deep Networks Be Normalized?
Picture a real-world audio setup: 12 loudspeakers connected in series, passing an audio signal along the chain. If each speaker amplifies the sound slightly (say, gain), by the 12th speaker , deafening howling feedback and distortion will shatter the speakers! Conversely, if each speaker slightly attenuates the sound (), by the 12th speaker , the signal vanishes into inaudibility.
The essence of Normalization is placing an agile Automatic Gain Controller (AGC) before and after each layer: no matter how wild the input signal is, it is immediately normalized to standard energy, guaranteeing stable numerical propagation across dozens of layers.
2.2.2 RMSNorm vs. Traditional LayerNorm: Discarding the Redundant Mean
Traditional LayerNorm involves two distinct operations:
- First, compute the feature mean: ;
- Second, center each element and divide by standard deviation
However, empirical research reveals: in deep networks, what stabilizes gradients is scaling by variance energy; subtracting the mean offers virtually zero mathematical value! Subtracting the mean requires an extra memory access pass across GPU SRAM, dragging down kernel speeds.
RMSNorm (Root Mean Square Normalization) makes an elegant simplification: compute the root mean square directly, and divide!
Where:
- (Epsilon) is a tiny protection constant (e.g., ) preventing division by zero;
- (Gamma) is a learnable scaling parameter of dimension
d_model(initialized to 1.0), allowing the network to adjust feature gains during training.
📐 Mathematical Deep Dive: Scale Invariance Proof of RMSNorm
Why does dividing directly by root mean square stabilize deep networks? Let us examine its Scale Invariance:
Suppose preceding layers unintentionally amplify the input signal by a factor of (turning into ):
Substitute into the RMSNorm formula:
Mathematical Takeaway: No matter how wildly previous layers amplify the signal, RMSNorm snaps its magnitude back to standard energy instantly!
2.2.3 Manual Numerical Walkthrough
To dispel any lingering doubt, let us calculate a concrete numerical example with 4 numbers: suppose a token's hidden vector is :
- Step 1 (Squaring):
- Step 2 (Mean):
- Step 3 (Square Root for RMS):
(this is its root mean square energy!)
- Step 4 (Energy Normalization):
Divide each value by 2.0:
Behold! Numbers that could have been arbitrarily large are instantly tamed within standard unit energy bounds.
2.2.4 Line-by-Line Code Implementation of RMSNorm
import torch
import torch.nn as nn
class RMSNorm(nn.Module):
def __init__(self, dim: int, eps: float = 1e-6):
super().__init__()
self.eps = eps # 防止除以 0 的极小保护值
# 可学习参数 gamma: 长度为 dim 的一维向量,初始全为 1.0
self.weight = nn.Parameter(torch.ones(dim))
def _norm(self, x: torch.Tensor) -> torch.Tensor:
# 1. x.pow(2).mean(-1, keepdim=True): 沿最后一个维度求平方的均值
# 2. torch.rsqrt: 即 1.0 / sqrt(...),求倒数平方根,GPU 上有专用硬件指令,一步完成!
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
def forward(self, x: torch.Tensor) -> torch.Tensor:
# 💡 导师经验提示: 均方根计算很容易在半精度 (float16) 下发生微小数值下溢。
# 因此业界标准实践是: 强转为 float32 计算均方根,算完后再转回张量原本的数据类型!
output = self._norm(x.float()).type_as(x)
# 乘以每个通道的可学习缩放系数 weight
return output * self.weight2.3 Building Block 2: RoPE Rotary Position Embedding (The Clock Hand Method)
This is the most geometrically elegant and intellectually satisfying operator in modern LLMs, and a frequent core topic in machine learning interviews.
2.3.1 Why Do LLMs Need Positional Awareness?
In human language, word order is decisive: "I do not like you" and "Do you not like me?" contain identical words but opposite meanings. Without position encodings, self-attention dot products cannot distinguish them.
- Sentence A: "I do not like you"
- Sentence B: "Do you not like me?"
Both sentences use identical words, but word order produces completely different meanings. However, self-attention dot product summation is mathematically permutation invariant! Without positional encoding, the model cannot distinguish word order.
2.3.2 The Fatal Flaw of Traditional Absolute Position Embeddings
In early models, the approach was rigid: "If you are the 1st word, issue an absolute badge labeled '1'; if you are the 2nd word, issue badge '2'..."
- Inability to Extrapolate (Cannot read long documents): If trained on sequences up to 1024 tokens, the model only possesses 1024 badges. Encountering token 1025 results in total failure;
- Violates Human Linguistic Intuition: When reading, humans do not care whether a verb is the 3421st word of a book; we only care about its relative distance to preceding nouns and modifiers.
2.3.3 The Clock Hand Epiphany: Relative Distance via Rotary Angles
How can we ensure the model focuses only on relative distance while extrapolating naturally? RoPE (Rotary Position Embedding) offers a brilliant insight:
Imagine a clock face:
- Suppose each token is a clock hand. Its position determines its rotation angle $m \cdot \theta;
- For example, token 1 points toward 1, while token 4 points toward 4;
- The angular difference between token 1 and token 4 is: $4\theta - 1\theta = 3\theta;
- If the entire sentence shifts by 10 positions (from token 11 to token 14):
- Token 11 rotates to , while token 14 rotates to $14\theta;
- Their angular difference remains strictly invariant: !
===================================================================================
RoPE 时钟旋转相对位置直观图解
===================================================================================
位置 m = 1 的词 (1点钟) 位置 m = 4 的词 (4点钟)
12 12
11 1 11 1
10 ▲ 2 10 2
9 │ 3 9 3 ──►
8 │ 4 8 4
7 5 7 5
6 6
【两根表针之间的相对夹角】: Δ = 4θ - 1θ = 3θ (只取决于它们之间相隔了 3 个字!)
===================================================================================📐 Mathematical Deep Dive: Euler's Formula and Relative Position Proof
Why does rotating clock hands cause inner product attention scores to depend strictly on relative distance? Let us prove this with higher mathematics:
1. Euler's Formula and Complex Multiplication
Leonhard Euler established one of the greatest formulas in human history:
In the complex plane, any 2D vector can be represented as a complex number . Multiplying by rotates the vector counterclockwise by angle :
In real Cartesian coordinate form, the new rotated coordinates are:
In matrix notation, this forms the familiar 2D rotation matrix:
2. Relative Distance Emergence in Dot Products
Now consider query vector at position and key vector at position . We apply positional rotations:
In complex space, the inner product of two vectors is :
Applying exponent addition rules:
Stop and gaze at this magnificent equation! Position indices and , through complex inner product, emerge cleanly as a relative distance difference: !
3. Multi-Frequency Waveband Spectral Analysis
In our model, per-head dimension is head_dim = 64, partitioned into 32 independent 2D rotation planes. The angular frequency for plane is:
The corresponding wavelength (number of tokens required to complete a full cycle) is:
- High-Frequency Plane 1 (): , wavelength tokens. Highly sensitive to adjacent word syntax!
- Low-Frequency Plane 32 (): , wavelength tokens! Captures long-range document discourse context.
2.3.4 Dissecting the Core Operator: rotate_half
In PyTorch implementation, how do we efficiently compute complex rotation without explicit matrix multiplies? Notice the second coordinate term: (-x2, x1).
Taking the second half of the vector, negating it, and prepending it is the famous operator rotate_half:
def rotate_half(x: torch.Tensor) -> torch.Tensor:
"""
将向量的前后两半拆开,后半截取负号并与前半截调换位置:
输入: [x1, x2]
输出: [-x2, x1]
"""
half_dim = x.shape[-1] // 2
x1 = x[..., :half_dim] # 前半截
x2 = x[..., half_dim:] # 后半截
return torch.cat((-x2, x1), dim=-1)
def apply_rotary_emb(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
"""
按照公式: x * cos(θ) + rotate_half(x) * sin(θ) 执行高速并行旋转
"""
cos = cos.unsqueeze(0).unsqueeze(2) # 调整广播维度以匹配 [Batch, Seq_Len, Heads, Head_Dim]
sin = sin.unsqueeze(0).unsqueeze(2)
return (x * cos) + (rotate_half(x) * sin)2.4 Building Block 3: SwiGLU Gated Feed-Forward Network (The Private Study)
2.4.1 Work Division: Seminars vs. Private Study
If a model only contains attention mechanisms, it acts like a room of people whispering rumors to each other without anyone having read actual books. In Transformers:
- Self-Attention handles "Information Gathering": Tokens ask peers what contextual intelligence they possess;
- Feed-Forward Network (FFN) handles "Knowledge Digestion": Tokens retire to their "Private Study", accessing deep long-term memory to process findings!
In modern LLMs, over 60% of all model parameters reside in the FFN; it is the true factual knowledge vault of the model!
2.4.2 Qualitative Leap: Traditional FFN vs. Modern SwiGLU
In legacy Transformers, the FFN structure was single-channeled and rigid:
All tokens pass through the exact same non-linear activation regardless of relevance.
Modern frontier architectures (LLaMA-3, Mistral, DeepSeek) exclusively use SwiGLU (Swish Gated Linear Unit):
- Content Pipe (): Carries expanded candidate representations;
- Gating Pipe (): Acts as an intelligent electromagnetic valve, generating continuous flow values between ;
Element-wise multiplication allows the network to: "close the valve against noise (multiply by 0), and open wide for vital clues (multiply by 1)"!
📐 Mathematical Deep Dive: Derivative and Self-Gating of SiLU
Why choose SiLU (Swish) over classic ReLU for the gate? Let us compare mathematical derivatives:
Differentiating :
Since the Sigmoid derivative satisfies , substituting and simplifying yields:
| Activation Function | Mathematical Formulation | Negative Half-Axis Property | Derivative Continuity & Dying Neuron Risk |
| ReLU | | Constant 0, hard cutoff | Discontinuous at ; negative inputs permanently "die" |
| GELU | | Smooth slight negative dip | Everywhere smooth, but computation involves costly error functions |
| SiLU (Swish)<br>[ Our Choice ] | | Smooth slight negative dip | Everywhere differentiable, self-gated, and native hardware friendly! |
This smooth differentiability ensures gradients flow uninterrupted during deep backpropagation, permanently resolving dying neuron hazards.
2.4.3 Line-by-Line Code Implementation of SwiGLU
import torch.nn.functional as F
class SwiGLUFeedForward(nn.Module):
def __init__(self, config):
super().__init__()
# 升维与门控投影矩阵 (无 bias,追求纯粹的高速线性特征)
self.w_gate = nn.Linear(config.d_model, config.d_ffn, bias=False) # 512 -> 1408
self.w_up = nn.Linear(config.d_model, config.d_ffn, bias=False) # 512 -> 1408
# 降维输出矩阵: 将知识浓缩回原本的 512 维空间
self.w_down = nn.Linear(config.d_ffn, config.d_model, bias=False) # 1408 -> 512
def forward(self, x: torch.Tensor) -> torch.Tensor:
# 1. gate 管道: 计算 0~1 的软阀门开度
gate_out = F.silu(self.w_gate(x))
# 2. up 管道: 抽取宽阔的知识特征
content = self.w_up(x)
# 3. 逐元素相乘 (阀门放行) 并降维输出
return self.w_down(gate_out * content)2.5 Building Block 4: GQA Grouped-Query Attention (The Detective Bureau)
Self-attention is the central engine enabling the model to comprehend context and exhibit intelligent behavior.
2.5.1 What Are ? The Detective Bureau Metaphor
Rather than reciting matrix formulas, let us visualize with a vivid detective scenario:
- (Query): The detective's missing-person bulletin ("Seeking words describing bright moonlight");
- (Key): Identity badges worn by individuals in the room ("I am a nighttime celestial body");
- (Value): Dossier evidence carried in each person's satchel ("Here is the descriptive prose of Tang poetry").
Self-attention is an orderly investigative retrieval process:
- Scoring (): The detective compares bulletin against badge via dot products, assessing relevance;
- Normalization (Softmax): Converts raw scores into percentage probabilities (e.g., 80% focus on Word A, 20% on Word B);
- Retrieval (): Blends satchel contents according to attention weights into a fused representation!
📐 Mathematical Deep Dive: Why Divide Attention by ? Variance Proof
This is one of the classic core interview questions in deep learning:
What disaster does that unassuming prevent? What happens if we omit it?
1. Mathematical Derivation of Inner Product Variance
Suppose query vector and key vector are -dimensional vectors ( in our model). Assume their elements are independent identically distributed with mean 0 and variance 1:
Their dot product is:
Let us derive the expectation and variance of inner product :
- Expectation:
- Single Term Variance:
- Total Variance (Sum of independent variables):
- Standard Deviation:
2. The Fatal Disaster of Omitting : Softmax Gradient Saturation
When , the standard deviation of raw dot products escalates to ! This means dot products frequently reach values like or .
The maximum exponent overwhelms all other terms in the Softmax denominator, driving maximum probability to while all other probabilities vanish to !
Let us examine the Softmax backward derivative:
- When : !
- When : !
Catastrophic Freeze: Gradients across the entire attention mechanism collapse to absolute zero, plunging backpropagation into total brain death!
3. The Life-Saving Antidote: Variance Normalization
Therefore, we must divide dot products by :
Mathematical Takeaway: Dividing by resets inner product variance back to , keeping inputs centered in the most sensitive gradient zone of Softmax!
2.5.2 Mathematical Limit of the Causal Mask
LLMs are autoregressive: token is strictly forbidden from peeking at future token . In the attention score matrix, future positions are masked with :
According to calculus limits of the exponential function:
After Softmax, attention weights on future tokens vanish to , completely barring temporal information leakage!
2.5.3 Why GQA? Detective Corps and Shared Secretaries
In Chapter 05 we will analyze autoregressive generation: at every step, past Key and Value tensors must reside in GPU VRAM (KV-Cache).
- Multi-Head Attention (MHA): 12 Query detective heads require 12 KV secretary pairs. For long sequences, KV-Cache memory consumes gigabytes of VRAM!
- Multi-Query Attention (MQA): All 12 detective heads share a single secretary. VRAM is slashed, but all heads see identical context, severely crippling model IQ.
- Grouped-Query Attention (GQA, Our Golden Choice): 12 Query heads share 4 KV secretary pairs (3 grouping)! Every 3 detectives share 1 dedicated secretary!
- VRAM footprint is instantly slashed by 66.7%!
- Semantic diversity and reasoning performance remain virtually unharmed!
| Attention Scheme | Query to KV Head Ratio | Per-Step KV Cache VRAM | Semantic Expressiveness & Speed Trade-off |
| Multi-Head Attention (MHA) | (e.g., 12 Q heads : 12 KV heads) | (full memory) | Highest expressiveness, but severe inference VRAM bottleneck |
| Multi-Query Attention (MQA) | (e.g., 12 Q heads : 1 KV head) | Drops to | Lowest memory, but noticeable quality degradation |
| Grouped-Query Attention (GQA)<br>[ Our Choice ] | (e.g., 12 Q heads : 4 KV heads, 3) | Slashes VRAM by 66.7% | The Industry Benchmark: Matches MHA quality with MQA speed! |
2.5.4 Line-by-Line Implementation of Attention
def repeat_kv(x: torch.Tensor, n_rep: int) -> torch.Tensor:
"""将 [Batch, Seq_Len, 4, 64] 沿着头维度重复复制 3 次,扩展为 [Batch, Seq_Len, 12, 64]"""
if n_rep == 1:
return x
batch, slen, n_kv, head_dim = x.shape
return (
x[:, :, :, None, :]
.expand(batch, slen, n_kv, n_rep, head_dim)
.reshape(batch, slen, n_kv * n_rep, head_dim)
)
class Attention(nn.Module):
def __init__(self, config):
super().__init__()
self.n_q_heads = config.n_q_heads # 8 个 Query 侦探头
self.n_kv_heads = config.n_kv_heads # 2 个 KV 秘书头
self.n_rep = config.n_q_heads // config.n_kv_heads # 组大小 = 4
self.head_dim = config.head_dim # 每个头的维度 = 64 (8 * 64 = 512)
# 线性映射矩阵 (全无 bias,保持干净纯粹)
self.wq = nn.Linear(config.d_model, config.n_q_heads * config.head_dim, bias=False)
self.wk = nn.Linear(config.d_model, config.n_kv_heads * config.head_dim, bias=False)
self.wv = nn.Linear(config.d_model, config.n_kv_heads * config.head_dim, bias=False)
self.wo = nn.Linear(config.n_q_heads * config.head_dim, config.d_model, bias=False)
def forward(self, x, cos, sin, mask=None, kv_cache=None):
b_sz, seq_len, _ = x.shape
# 1. 将 512 维特征分别投影出 Q, K, V,并拆分为多头
xq = self.wq(x).view(b_sz, seq_len, self.n_q_heads, self.head_dim)
xk = self.wk(x).view(b_sz, seq_len, self.n_kv_heads, self.head_dim)
xv = self.wv(x).view(b_sz, seq_len, self.n_kv_heads, self.head_dim)
# 2. 为 Q 和 K 注入时钟旋转位置编码 (RoPE)
xq = apply_rotary_emb(xq, cos, sin)
xk = apply_rotary_emb(xk, cos, sin)
# 3. 维护增量推理 KV-Cache (第 05 章详细讲解)
new_kv = None
if kv_cache is not None:
prev_k, prev_v = kv_cache
xk = torch.cat([prev_k, xk], dim=1) if prev_k is not None else xk
xv = torch.cat([prev_v, xv], dim=1) if prev_v is not None else xv
new_kv = (xk, xv)
# 4. GQA 广播: 把 2 个 KV 头复制 4 遍,对齐 8 个 Query 头
xk_expanded = repeat_kv(xk, self.n_rep)
xv_expanded = repeat_kv(xv, self.n_rep)
# 5. 调整维度以符合 PyTorch 官方底层硬件加速格式: [Batch, Heads, Seq_Len, Head_Dim]
xq = xq.transpose(1, 2)
xk_expanded = xk_expanded.transpose(1, 2)
xv_expanded = xv_expanded.transpose(1, 2)
# 6. 调用超强底层硬件融合内核 (FlashAttention / SDPA)
is_causal = (mask is None and seq_len > 1 and kv_cache is None)
output = F.scaled_dot_product_attention(
xq, xk_expanded, xv_expanded,
attn_mask=mask,
dropout_p=0.0,
is_causal=is_causal # 自动构建严密的下三角因果防偷看掩码!
)
# 7. 多头汇报汇总: 还原回 [Batch, Seq_Len, 512]
output = output.transpose(1, 2).contiguous().view(b_sz, seq_len, -1)
return self.wo(output), new_kv2.6 Two Crowning Touches: Residual Connections and Weight Tying
Before assembling the building blocks into a skyscraper, we must appreciate two brilliant innovations in modern deep learning.
2.6.1 Residual Connections: The Non-Stop Express Elevator
Inside each layer, you encounter this signature line: h = x + attn_out
- Why not simply write
h = attn_out? Why add the originalx? - The Broken Telephone Metaphor: Imagine 12 people whispering a message down a line. By the 12th person, the original meaning is completely unrecognizable.
- The Express Elevator: The residual addition
x + ...builds a non-stop elevator alongside the 12-story building. The raw input signal travels straight to upper floors without distortion, while backpropagation gradients flow losslessly downward!
2.6.2 Weight Tying: The Mirror Pact of Input and Output Vocabularies
In our 0.04B model:
- The input layer has a
token_embeddingsmatrix mapping 4096 tokens to 512 dimensions (parameters: ); - The output layer has an
lm_headprojection mapping 512 dimensions back to 4096 vocabulary scores (parameters: ).
Together, these two matrices consume 4.19M parameters—a massive 11.0% of our entire 0.04B model! Since one maps words to vectors and the other maps vectors back to words, why keep two duplicate matrices?
self.lm_head.weight = self.token_embeddings.weightWe force them to share the exact same physical memory matrix! The model instantly saves 2.10 million redundant parameters, shrinking VRAM and tightly aligning token representation spaces!
2.7 Final Assembly: Building the Complete MiniLLaMAForCausalLM
All foundations, beams, and windows are ready. We now assemble them into a full 35.93M parameter language model!
2.7.1 The Single-Layer Round Table: TransformerBlock
class TransformerBlock(nn.Module):
def __init__(self, config):
super().__init__()
# 注意力层及其门前音量平衡器
self.attn_norm = RMSNorm(config.d_model, eps=config.norm_eps)
self.attn = Attention(config)
# 前馈网络层及其门前音量平衡器
self.ffn_norm = RMSNorm(config.d_model, eps=config.norm_eps)
self.ffn = SwiGLUFeedForward(config)
def forward(self, x, cos, sin, mask=None, kv_cache=None):
# 1. 第一阶段: 前置归一化 + 注意力情报交互 + 残差直连
attn_out, new_kv = self.attn(self.attn_norm(x), cos, sin, mask=mask, kv_cache=kv_cache)
h = x + attn_out
# 2. 第二阶段: 前置归一化 + 前馈书房独立思考 + 残差直连
out = h + self.ffn(self.ffn_norm(h))
return out, new_kv2.7.2 The Complete Causal Language Model: MiniLLaMAForCausalLM
class MiniLLaMAForCausalLM(nn.Module):
def __init__(self, config):
super().__init__()
self.config = config
# 1. 词嵌入映射
self.token_embeddings = nn.Embedding(config.vocab_size, config.d_model)
# 2. 预先计算 RoPE 旋转角度查找表,注册为非求导缓存 (Buffer)
cos, sin = precompute_freqs_cis(config.head_dim, config.max_seq_len, config.rope_theta)
self.register_buffer("cos_cached", cos, persistent=False)
self.register_buffer("sin_cached", sin, persistent=False)
# 3. 堆叠 12 层 TransformerBlock 圆桌研讨层
self.layers = nn.ModuleList([TransformerBlock(config) for _ in range(config.n_layers)])
# 4. 终层归一化平滑
self.norm = RMSNorm(config.d_model, eps=config.norm_eps)
# 5. 语言模型预测输出头
self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False)
# 6. 开启权重绑定: 输入输出共享词典矩阵
if config.tie_word_embeddings:
self.lm_head.weight = self.token_embeddings.weight
def forward(self, input_ids, labels=None, past_key_values=None, start_pos=0):
b_sz, seq_len = input_ids.shape
# 第一步: 查表获取初始 512 维特工档案袋
h = self.token_embeddings(input_ids)
# 第二步: 切出当前窗口对应的 RoPE 旋转角度
cos = self.cos_cached[start_pos : start_pos + seq_len]
sin = self.sin_cached[start_pos : start_pos + seq_len]
new_past_key_values = [] if past_key_values is not None else None
# 第三步: 顺次走过 12 层圆桌会议的深度研讨
for idx, layer in enumerate(self.layers):
layer_kv = past_key_values[idx] if past_key_values is not None else None
h, updated_kv = layer(h, cos, sin, kv_cache=layer_kv)
if new_past_key_values is not None:
new_past_key_values.append(updated_kv)
# 第四步: 终层音量稳定
h = self.norm(h)
# 第五步: 投影回 4096 个备选词的未归一化打分
logits = self.lm_head(h)
# 第六步: 如果给定了真实标签 labels,计算自回归交叉熵损失
loss = None
if labels is not None:
# 错位 1 个 Token 计算自回归目标: 用当前词的打分去预测下一个真实的词!
shift_logits = logits[..., :-1, :].contiguous()
shift_labels = labels[..., 1:].contiguous()
loss = F.cross_entropy(
shift_logits.view(-1, self.config.vocab_size),
shift_labels.view(-1)
)
return logits, loss, new_past_key_values📐 Mathematical Deep Dive: From Maximum Likelihood to Autoregressive Cross-Entropy Loss
Why do lines 385~390 in the code shift logits and labels by 1 position before computing cross-entropy? Let us unveil the information theory roots:
1. Joint Probability Distribution of Language Models
Given a text sequence of length , , the core objective of an LLM is learning the joint probability distribution:
2. Maximum Likelihood Estimation (MLE) and Negative Log-Likelihood (NLL)
In statistics, we seek parameters that maximize the probability of real training corpora (Maximum Likelihood Estimation):
To avoid floating-point underflow from multiplying thousands of tiny probabilities, we take the natural logarithm and negate it to form Negative Log-Likelihood (NLL) minimization:
3. Why Is It Identical to Cross-Entropy?
At step , the true next token is a one-hot probability vector . Cross-entropy between predicted distribution and ground-truth is:
Since is 1 only at and 0 everywhere else, the formula collapses to:
Mathematical Conclusion: Taking the mean loss over the sequence is precisely what our PyTorch code executes: F.cross_entropy(shift_logits.view(-1, V), shift_labels.view(-1))!
2.8 Mentor Summary and Hands-on Lab
Through this systematic assembly, the inner workings of modern LLMs are completely demystified:
- Pre-RMSNorm: Like an agile mixer console, eliminates mean calculations with strict scale invariance to stabilize deep signals;
- RoPE: Uses Euler complex multiplications to naturally encode relative distance differences in inner products;
- SwiGLU: Smoothly differentiable SiLU gating delivers high factual knowledge storage density;
- GQA: Resets inner product variance to 1.0 with and saves 75% KV-Cache VRAM via 4 grouping;
- Residual Connections & Weight Tying: Serve as express elevators and mirrored vocabularies, making the model deep yet lean.
💡 Hands-on Lab: Awakening Your 0.04B Model
Step 1: Verify Environment Dependencies (Install PyTorch)
Our model construction relies on native PyTorch tensor operations. If your terminal reports No module named 'torch', install PyTorch according to your hardware environment.
pip install torchStep 2: Run in Project Root Directory (Troubleshooting Clinic ⚠️)
Before running tests, ensure your terminal working directory is the project root (llm-start), not the subdirectory src:
- Incorrect ❌: Navigating into
llm-start\srcand executingpython -c "from src.model import ..."; - Correct ✅: Keeping terminal at project root
d:\code\project\llm-startwhen running commands.
Method A: Recommended Automated Unit Test (Zero Friction)
Run the factory acceptance test script tests/test_model.py:
python tests/test_model.pyExpected Terminal Console Output:
============================================================
Mini-LLaMA-0.04B 架构出厂体检与前向验证
============================================================
1. 正在初始化模型 (隐层维度: 512, 层数: 12, 词表: 4096)...
2. 模型总参数量核算: 35,926,528 个参数 (35.93 M)
3. 模拟输入张量形状: torch.Size([1, 5]) (Batch=1, Seq_Len=5)
4. 模型输出 Logits 形状: torch.Size([1, 5, 4096]) (符合预期: [1, 5, 4096])
🎉 恭喜!Mini-LLaMA-0.04B 神经网络架构前向推理与形状校验全部通过!✅
============================================================Method B: Step-by-Step Experience in Python REPL
To inspect intermediate tensors interactively, launch python in your terminal and run:
# 1. 导入配置与模型定义
from src.model import MiniLLaMAForCausalLM, MiniLLaMAConfig
import torch
# 2. 实例化配置与模型
config = MiniLLaMAConfig()
model = MiniLLaMAForCausalLM(config)
# 3. 打印真实参数量
total_params = sum(p.numel() for p in model.parameters())
print(f"总参数量: {total_params:,} ({total_params/1e6:.2f} M)")
# 终端打印: 总参数量: 35,926,528 (35.93 M)
# 4. 模拟送入一句话 (Batch=1, 长度为 5 的假 Token 序列)
dummy_input = torch.tensor([[1, 232, 58, 209, 2]])
logits, loss, _ = model(dummy_input)
print(f"输出 Logits 形状: {logits.shape}")
# 终端打印: 输出 Logits 形状: torch.Size([1, 5, 4096])Observe the terminal printouts:
- Is the parameter count precisely the 35.93 M we calculated?
- Is the output tensor shape cleanly formatted as
[1, 5, 4096]?
The thinking cortex is forged! In the next chapter, we construct the high-speed data pipeline that fuels this cortex with knowledge: Chapter 03 | High-Performance Data Pipeline and Memory Mapping.
REFERENCES
References
Series
Building an LLM from scratch