拆解大模型二:Transformer 最核心的设计,其实你高中就学过

拆解大模型二:Transformer 最核心的设计,其实你高中就学过

Attention 机制,很多人觉得它神秘、复杂、充满线性代数。

但如果我告诉你,它的核心运算------两个向量做点积,算相似度------是你高中数学就见过的东西,你信吗?

<math xmlns="http://www.w3.org/1998/Math/MathML"> a ⃗ ⋅ b ⃗ = ∣ a ⃗ ∣ ∣ b ⃗ ∣ cos ⁡ θ \vec{a} \cdot \vec{b} = |\vec{a}||\vec{b}|\cos\theta </math>a ⋅b =∣a ∣∣b ∣cosθ

两个向量夹角越小,点积越大,越"对齐"。

Attention 的核心就是这个。剩下的,全是围绕这个操作搭起来的工程结构。

这篇从这个最朴素的直觉出发,一路推到代码实现。


一、先说清楚问题:模型凭什么读懂上下文

复制代码
猫坐在垫子上,它很舒服。

人读这句话,一眼知道"它"指的是"猫"。但模型只是在逐个预测 Token------它怎么建立这种跨越距离的关联?

Transformer 之前,主流答案是 RNN:从左到右一词一词地读,每步更新一个"记忆状态"往后传。

听起来合理,但有个致命问题:信息随距离衰减。 读到第 50 个词,第 1 个词的信息已经被"转手"几十次,稀释得差不多了。

试试这个:

markdown 复制代码
小明三岁时随父母从东北搬到上海,在那里长大、上学、工作、娶妻生子,
一晃三十年。有一天他突然很想吃____

要填这个空,你得想起"东北"------但它在三十多个词之前。RNN 大概率已经把它忘了。

Attention 的解法干脆得出人意料:别靠传递,需要什么就直接回头看。 预测每个位置时,模型可以直接访问序列里任意位置的信息,跟距离无关。


一、Q、K、V:用类比理解

每个 Token 在进入 Attention 时,会生成三个向量:Query(Q)、Key(K)、Value(V)

类比图书馆找书:

概念 图书馆类比 在模型里的含义
Query 你的需求:"我想找量子力学入门" 当前 Token 在问:"我需要什么信息?"
Key 书的标签:书名、关键词 其他 Token 在说:"我能提供什么?"
Value 书的实际内容 其他 Token 真正携带的语义信息

Attention 做的事:拿 Q 去跟所有 K 比相似度,相似度越高,就从对应的 V 里取越多信息,最后加权混合。


二、计算过程:三步走

第一步:Q 和 K 算相似度(点积)

<math xmlns="http://www.w3.org/1998/Math/MathML"> score ( Q , K ) = Q ⋅ K \text{score}(Q, K) = Q \cdot K </math>score(Q,K)=Q⋅K

点积越大,说明两个向量越"对齐",相似度越高。

但点积值会随向量维度 <math xmlns="http://www.w3.org/1998/Math/MathML"> d k d_k </math>dk 增大而变大,导致 Softmax 进入饱和区、梯度消失。所以要除以 <math xmlns="http://www.w3.org/1998/Math/MathML"> d k \sqrt{d_k} </math>dk 做缩放:

<math xmlns="http://www.w3.org/1998/Math/MathML"> score = Q ⋅ K d k \text{score} = \frac{Q \cdot K}{\sqrt{d_k}} </math>score=dk Q⋅K

第二步:Softmax 变成权重

<math xmlns="http://www.w3.org/1998/Math/MathML"> weights = softmax ( scores ) \text{weights} = \text{softmax}(\text{scores}) </math>weights=softmax(scores)

归一化成加起来等于 1 的概率分布。

第三步:用权重对 V 加权求和

<math xmlns="http://www.w3.org/1998/Math/MathML"> output = ∑ i weights i ⋅ V i \text{output} = \sum_i \text{weights}_i \cdot V_i </math>output=∑iweightsi⋅Vi

写成矩阵形式:

<math xmlns="http://www.w3.org/1998/Math/MathML"> Attention ( Q , K , V ) = softmax ( Q K T d k ) V \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V </math>Attention(Q,K,V)=softmax(dk QKT)V

三步总结:算相似度 → 变权重 → 加权求 V


三、代码实现:从零写一个 Attention

光看公式不过瘾,直接用 NumPy 实现一遍:

py 复制代码
import numpy as np

def softmax(x):
    # 数值稳定的 softmax
    e_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
    return e_x / e_x.sum(axis=-1, keepdims=True)

def attention(Q, K, V):
    """
    Q: (seq_len, d_k)
    K: (seq_len, d_k)
    V: (seq_len, d_v)
    """
    d_k = Q.shape[-1]
    
    # 第一步:算相似度,缩放
    scores = Q @ K.T / np.sqrt(d_k)          # (seq_len, seq_len)
    
    # 第二步:Softmax 得到权重
    weights = softmax(scores)                  # (seq_len, seq_len)
    
    # 第三步:加权求和
    output = weights @ V                       # (seq_len, d_v)
    
    return output, weights

# 模拟一个 4 个 Token、维度为 8 的序列
np.random.seed(42)
seq_len, d_k, d_v = 4, 8, 8

Q = np.random.randn(seq_len, d_k)
K = np.random.randn(seq_len, d_k)
V = np.random.randn(seq_len, d_v)

output, weights = attention(Q, K, V)

print("Attention 权重矩阵(每行加起来 = 1):")
print(np.round(weights, 3))
print("\n输出 shape:", output.shape)

运行结果:

md 复制代码
Attention 权重矩阵(每行加起来 = 1):
[[0.23  0.41  0.18  0.18]
 [0.31  0.22  0.28  0.19]
 [0.19  0.35  0.27  0.19]
 [0.28  0.19  0.31  0.22]]

输出 shape: (4, 8)

权重矩阵的第 i 行,就是第 i 个 Token 对序列里所有位置的注意力分配。比如第 0 个 Token 最关注第 1 个位置(权重 0.41),说明它从那里取了最多的信息。


四、Q、K、V 从哪来

上面例子里 Q、K、V 是随机生成的,真实模型里它们是从 Token 的 embedding 变换来的:

md 复制代码
# 三个可学习的权重矩阵
W_Q = np.random.randn(d_model, d_k)
W_K = np.random.randn(d_model, d_k)
W_V = np.random.randn(d_model, d_v)

# 从输入 embedding 生成 Q、K、V
Q = embedding @ W_Q    # (seq_len, d_k)
K = embedding @ W_K    # (seq_len, d_k)
V = embedding @ W_V    # (seq_len, d_v)

<math xmlns="http://www.w3.org/1998/Math/MathML"> W Q W_Q </math>WQ、 <math xmlns="http://www.w3.org/1998/Math/MathML"> W K W_K </math>WK、 <math xmlns="http://www.w3.org/1998/Math/MathML"> W V W_V </math>WV 是训练出来的参数。模型通过训练学会的,就是"用什么样的变换方式,能让 Q 和 K 的匹配最有意义"。


五、为什么要多头

实际 Transformer 用的是 Multi-Head Attention------同时跑多个 Attention head。

原因是:同一句话里存在多种性质不同的关联,单个 head 一次只能专注一种。

小明告诉小红他明天不来了

  • "他" → "小明"(指代关系)
  • "明天" → "不来"(时间-事件)
  • "告诉" → "小明"(动作-主语)

多头让每个 head 各自学一类关联,最后把所有 head 的输出拼起来。GPT-3 用了 96 个 head,可解释性研究者现在还在挖每个 head 分别学到了什么。

代码层面就是把上面的 attention() 跑 h 次,用不同的 <math xmlns="http://www.w3.org/1998/Math/MathML"> W Q i , W K i , W V i W_Q^i, W_K^i, W_V^i </math>WQi,WKi,WVi,最后 concat:

py 复制代码
def multi_head_attention(X, W_Qs, W_Ks, W_Vs, W_O):
    heads = []
    for W_Q, W_K, W_V in zip(W_Qs, W_Ks, W_Vs):
        Q = X @ W_Q
        K = X @ W_K
        V = X @ W_V
        head, _ = attention(Q, K, V)
        heads.append(head)
    
    # 拼接所有 head 的输出
    concat = np.concatenate(heads, axis=-1)   # (seq_len, h * d_v)
    
    # 最后再做一次线性变换
    return concat @ W_O

六、代价:O(n²) 的计算复杂度

Attention 很强,但计算量随序列长度平方增长------序列翻倍,计算量变四倍。

这就是为什么早期模型上下文只有 2K、4K Token,现在做到几十万 Token 背后有大量优化工作。

Flash Attention 是其中最有代表性的:通过重新排列矩阵计算顺序,大幅减少 HBM(显存)读写次数,在不改变数学结果的情况下把速度提升几倍。KV Cache 则是推理阶段的关键优化------避免每次生成新 Token 时重复计算历史的 K、V。

这两个方向目前还是研究热点,之后单独开篇聊。


小结

回到开头那句话:Attention 最核心的运算,就是高中向量点积。

两个向量越对齐,点积越大,相似度越高,就从对应位置取更多信息。剩下的缩放、Softmax、加权求和,都是围绕这个直觉搭起来的工程结构。

Attention 解决的核心问题:让模型在预测时,直接访问序列任意位置的信息,不受距离限制。

计算三步:Q·K 算相似度 → Softmax 变权重 → 对 V 加权求和。

Q、K、V 从 embedding 线性变换来,变换矩阵是训练参数。

Multi-Head 是多个 Attention 并行,捕捉不同类型的语言关联。

代价是 O(n²) 复杂度,Flash Attention 和 KV Cache 是对应的工程解法。


下一篇看 Transformer 的整体结构:

Embedding 是什么、FFN 在做什么、残差连接为什么重要------这些组件怎么拼成一个完整的大模型?

相关推荐
gustt2 小时前
MCP协议进阶:构建多工具Agent实现智能查询与浏览器交互
人工智能·agent·mcp
Halo咯咯2 小时前
Claude Code 的工程哲学:缓存与工具设计的真实教训 | 经验分享
人工智能
风象南3 小时前
最适合新手先装的 20 个 OpenClaw Skills 来了!
人工智能
小兵张健14 小时前
35岁程序员的春天来了
人工智能
大怪v14 小时前
AI抢饭?前端佬:我要验牌!
前端·人工智能·程序员
冬奇Lab14 小时前
OpenClaw 深度解析(六):节点、Canvas 与子 Agent
人工智能·开源
刀法如飞15 小时前
AI提示词框架深度对比分析
人工智能·ai编程
IT_陈寒17 小时前
Python开发者必知的5大性能陷阱:90%的人都踩过的坑!
前端·人工智能·后端
1G18 小时前
openclaw控制浏览器/自动化的playwright MCP + Mcporter方案实现
人工智能