惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
WordPress大学
WordPress大学
N
Netflix TechBlog - Medium
宝玉的分享
宝玉的分享
V
Visual Studio Blog
S
Securelist
P
Palo Alto Networks Blog
A
Arctic Wolf
T
Tor Project blog
P
Proofpoint News Feed
I
InfoQ
博客园 - 三生石上(FineUI控件)
T
Threat Research - Cisco Blogs
G
GRAHAM CLULEY
M
MIT News - Artificial intelligence
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Security Blog
Microsoft Security Blog
MongoDB | Blog
MongoDB | Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
Apple Machine Learning Research
Apple Machine Learning Research
S
Secure Thoughts
Cyberwarzone
Cyberwarzone
Blog — PlanetScale
Blog — PlanetScale
Simon Willison's Weblog
Simon Willison's Weblog
博客园 - 【当耐特】
大猫的无限游戏
大猫的无限游戏
腾讯CDC
Latest news
Latest news
Project Zero
Project Zero
V
Vulnerabilities – Threatpost
Y
Y Combinator Blog
S
SegmentFault 最新的问题
Schneier on Security
Schneier on Security
C
CERT Recently Published Vulnerability Notes
W
WeLiveSecurity
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
L
LINUX DO - 热门话题
N
News and Events Feed by Topic
PCI Perspectives
PCI Perspectives
C
Cisco Blogs
Application and Cybersecurity Blog
Application and Cybersecurity Blog
爱范儿
爱范儿
T
The Exploit Database - CXSecurity.com
The Last Watchdog
The Last Watchdog
人人都是产品经理
人人都是产品经理
GbyAI
GbyAI
Know Your Adversary
Know Your Adversary
U
Unit 42

I'm OWenT

新版本libatapp的连接管理——从etcd服务发现到拓扑驱动的自动重连 新版本libatbus的设计变更——从树形路由到拓扑驱动 Protobuf又一坑 - C++标准和ABI兼容性 AI真好用-给Blog主题统一加mermaid,chart.js,excalidraw,draw.io的多种引入方式支持 给内网部署Squid-通用HTTP下载缓存 UE使用CodeChecker和clang-tidy生成静态分析报告 找出UE的循环依赖 C++小协程栈和临时变量及作用域的栈溢出问题分析 游戏服务的可观测性能力建设(C++生态) 指标上报的多线程优化和多拉取源点优化 协程(libcopp)的Channel功能和CPU命中率优化 通用RPC代码生成器 实现strong_rc_ptr(比shared_ptr更快的引用计数智能指针) 手夯一个STL allocator和对象内存分析组件 std::condition_variable 的信号丢失问题 踩坑一处(GCC)STL std::async 实现BUG导致的crash问题 GCC 14的一个warning to error BUG 给xresloader(Excel导表工具)增强UE读表支持(包含蓝图,Blueprint) Opentelemetry社区在gRPC的几个链接问题(静态库和动态库混用,musl工具链,符号裁剪) Excel转表工具(xresloader)的新验证器(验证外部Excel和文本数据,唯一性和自定义规则) protobuf v22和gRPC v1.55版本升级的依赖变化和upb适配 关于protobuf近期版本(v20/v3.20+)和 gRPC v1.54版本在某些编译环境下的一些链接和编译问题 xresloader-Excel导表工具链的近期变更汇总 打通游戏服务端框架的C++20协程改造的最后一环 Opentelemetry-cpp的Logs模块标准更新(涉及近期版本:1.8-1.9的BREAK CHANGES) 给cmake-toolset和工具链(curl等)加HTTP/2和HTTP/3支持 又开新坑之 coredns 插件: nftables和filter 关于opentelemetry-cpp社区对于C++ Head Only组件单例和符号可见性的讨论小记 填个转表工具 xresloader 去年的坑(数组尾部裁剪) 集成 upb 和 lua binding 的踩坑小记 libcopp对C++20协程的接入和接口设计 再度优化GCC、LLVM、Clang、libc++、libc++abi等套件的构建脚本 游戏服务的分布式事务优化(二)- 事务管理 游戏服务的分布式事务优化(一)- Write Ahead Log(WAL) 模块 记录一些bazel适配用编译选项 测试现代化硬件C++浮点数性能和一致性 适配Boringssl和OpenSSL 3.0 近期cmake-toolset的一些适配问题 C++20 Text Formatting/fmtlib 适配问题小记 再次重构LLVM+Clang+libcxx+libc++abi+其他相关工具的构建流程 重构基于CMake的构建工具链 新版GCC和LLVM+Clang终于Release啦 折腾一下nftables下的双拨 [C++20] Module partitions和符号交叉引用(声明和实现分离) [Rust] 实现一个线程安全且迭代器可以保存的链表 基于protobuf的代码生成 几个使用protobuf中C++接口的Arena的坑 Amazon Aurora DB存储引擎论文阅读小记 近期对libatapp的一些优化调整(增加服务发现和连接管理,支持yaml等) xresloader转表工具链增加了一些新功能(map,oneof支持,输出矩阵,基于模板引擎的加载代码生成等) 在游戏服务器中使用分布式事务 libcopp接入C++20 Coroutine和一些过渡期的设计 libatbus 的大幅优化 nftables初体验 容器配置开发环境小计 PALM Tree - 适合多核并发架构的B+树 - 论文阅读小记 跨平台协程库 - libcopp 简介 C++20 Coroutine 性能测试 (附带和libcopp/libco/libgo/goroutine/linux ucontext对比) 尝鲜Github Action 一些xresloader(转表工具)的改进 protobuf、flatbuffer、msgpack 针对小数据包的简单对比 协程框架(libcopp) 小幅优化 Excel转表工具(xresloader) 增加protobuf插件功能和集成 UnrealEngine 支持 Anna(支持任意扩展和超高性能的KV数据库系统)阅读笔记 C++20 Coroutine libcopp merge boost.context 1.69.0 Google去中心化分布式系统论文三件套(Percolator、Spanner、F1)读后感 Rust玩具-企业微信机器人通用服务 使用ELK辅助监控开发测试环境服务质量和问题定位 2018年的新通用伪随机数算法(xoshiro / xoroshiro)的C++(head only)实现 Webpack+vue+boostrap+ejs构建Web版GM工具 Rust的第二次接触-写个小服务器程序 理解和适配AEAD加密套件 atsf4g-co的进化:协程框架v2、对象路由系统和一些其他细节优化 协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比 可执行文件压缩 初识Rust 使用restructedtext编写xresloader文档 atframework的etcd模块化重构 C++的backtrace ECDH椭圆双曲线(比DH快10倍的密钥交换)算法简介和封装 protobuf-net的动态Message实现 pbc的proto3接入 atgateway内置协议流程优化-加密、算法协商和ECDH 整理一波软件源镜像同步工具+DevOps工具 Blog切换到Hugo libcopp v2的第一波优化完成 libcopp(v2) vs goroutine性能测试 libcopp的线程安全、栈池和merge boost.context 1.64.0 GCC 7和LLVM+Clang+libc++abi 4.0的构建脚本 libatbus的几个藏得很深的bug 用cmake交叉编译到iOS和Android 开源项目得一些小维护 atapp的c binding和c#适配 对象路由系统设计 2016年总结 近期的一个协程流程BUG 重写了llvm+clang+libc++和libc++abi的构建脚本 atsf4g完整游戏工程示例|I'm OWenT atframework基本框架已经完成|I'm OWenT
大语言模型的基石:Transformer 入坑笔记(三) - 注意力机制和 Transformer|I'm OWenT
2026-06-28 · via I'm OWenT


背景

接上文 《大语言模型的基石:Transformer 入坑笔记(二) - 基本原理和 Word Embeddings》,继续我们的 Attention Is All You Need/Transformer 学习之旅。

首先简单了解下传统的方案。

卷积神经网络(CNN)

卷积神经网络(CNN)似乎更适合静态数据(比如图片处理、提取特征等)。 所谓静态数据,是指每个数据组都单独和目标矩阵运算,通过卷积层、池化层、全连接层等输出。 每个数据组都单独运算所以可以大规模并发,但是数据组之间也缺乏关联。 我大概看了下原理,和我们要关注的 Transformer 关系不大,先略过了。

循环神经网络(RNN)

我们处理文本的时候,需要让神经网络知道上下文关系。 比如一句话: “我上午要去图书馆借书,下午去参加比赛。晚饭前我会把它还掉。” 那么在这里,系统需要知道“它”指的是“书”。

传统循环神经网络(RNN)的方法就是把后续数据叠加到前文输入里来传递上下文表达。 这里不详细铺开循环神经网络(RNN)的原理和过程,因为不是我们要关注的重点。大致了解一下流程和问题即可。

比如我们要把 I love llamas 翻译成中文。 典型的 RNN encoder-decoder 会先把源句子读成隐藏状态,Decoder 再按顺序生成目标 Token。每一步都会依赖上一步的隐藏状态和已经生成的 Token。

StepInputOutput
1I love llamas
2I love llamas
3I love llamas 我爱羊驼

注意: 词向量、隐藏状态以及最终计算输出的w、b矩阵都是不同的。

显然这里有两个问题。一是同一个序列里的每一步都依赖上一步结果,并行度受限。二是内容太长时,早期信息会逐渐被隐藏状态压缩掉,长距离依赖容易丢。 当然有一些方法一定程度缓解这个问题,但是也不是我们要关注的 Transformer 的重点就也略过了。

Transformer

那前面两种模型都有各自的问题。俗话说小孩子才做选择,大人我全都要。怎么办呢?于是 Transformer 来了。

这有个非常好的工具 Transformer Explainer,可以把 Transformer 里的各个步骤拆开看,很适合理解整体流程。 这个工具甚至还发了篇 Paper:《Transformer Explainer: Learning LLM Transformers with Interactive Visual Explanation and Experimentation》

注意力机制并不是 《Attention Is All You Need》 的首创,之前已经有人把它用来缓解传统神经网络在长距离依赖上的问题。 这篇论文真正激进的地方,是把循环和卷积从主干里拿掉,只用注意力搭出完整序列模型。还是先贴 Transformer 的基本框架:

图 1:Transformer 模型架构(来自 《Attention Is All You Need》)。左侧为 Encoder,右侧为 Decoder,Encoder block 和 Decoder block 各重复 N 次。

Positional Encoding

一上来,仍然是把输入数据做 Embedding。具体方式和原理前面解释过了,这里就不复述了。 然后差异点是,现在还要融合 Positional Encoding 矩阵,表示词所在的位置。

2610-add-positional-encoding.png

为什么需要 Positional Encoding 矩阵呢?因为 Transformer 的注意力层本身不会天然知道 Token 的先后顺序;如果不额外加入位置信息,模型很难区分“我爱你”和“你爱我”这种同词不同序的输入。Positional Encoding 矩阵的公式如下:

$$ \begin{aligned} \mathbf{PE}_{(\text{pos}, 2i)} &= \sin\left(\frac{\text{pos}}{10000^{2i/d_{\text{model}}}}\right) \\ \mathbf{PE}_{(\text{pos}, 2i+1)} &= \cos\left(\frac{\text{pos}}{10000^{2i/d_{\text{model}}}}\right) \end{aligned} $$

如果序列长度为 $L$,模型维度为 $d_{model}$,位置编码矩阵 $PE$ 的维度就是 $L \times d_{model}$。然后:

  • $pos$ :词在序列中的位置(从 0 开始)。其中序列长度为 $L$,则 $0 \le pos < L$
  • $d_{model}$ :模型的总维度(如 512、768、1024 等,Transformer 论文里是 512)
  • $i$ :维度索引对($2i$ 和 $2i+1$ 成对出现,$0 \le i < d_{model}/2$)。

位置公式不是定死的,只要满足几个条件即可。

  • 唯一标识: 每个位置必须有独一无二的编码向量
  • 相对距离可感知: 模型能判断“相隔 $k$ 个位置”的关系,而不仅是绝对位置
  • 平滑过渡: 相邻位置的编码应该相近,反映位置的连续性
  • 值域有界: 编码值不能过大或过小,避免破坏词嵌入的数值分布

Transformer 的方案使用的是正弦和余弦函数,值域范围是 $-1 \le PE \le 1$,距离感知推导如下:

利用三角函数加法公式:$\sin(a+b) = \sin a \cos b + \cos a \sin b$,令 $a = pos \cdot w_i, b = k \cdot w_i$(其中 $w_i = \frac{1}{10000^{2i/d_{\text{model}}}}$),则:

$$ \begin{aligned} \mathbf{PE}_{(\text{pos}+k, 2i)} &= \sin(\text{pos}\cdot w_i + k\cdot w_i) \\ &= \underbrace{\sin(\text{pos}\cdot w_i)}_{\mathbf{PE}_{(\text{pos},2i)}} \underbrace{\cos(k\cdot w_i)}_{\text{仅与}k\text{有关}} + \underbrace{\cos(\text{pos}\cdot w_i)}_{\mathbf{PE}_{(\text{pos},2i+1)}} \underbrace{\sin(k\cdot w_i)}_{\text{仅与}k\text{有关}} \end{aligned} $$

同理: $\mathbf{PE}_{(\text{pos}+k, 2i+1)} = \mathbf{PE}_{(\text{pos},2i+1)}\cos(k\cdot w_i) - \mathbf{PE}_{(\text{pos},2i)}\sin(k\cdot w_i)$

所以是满足这些条件的。不过要注意的是,原始公式没有“超过某个位置就不能用”的硬上限;它可以外推,但最长波长的量级大约是 $10000 \times 2\pi$,序列长度远超训练范围后效果不能只靠公式保证。现代长上下文模型通常还会配合 RoPE、ALiBi、位置插值、分块或稀疏注意力等方案。 最后,显然对确定的总维度,这个位置编码矩阵是可以提前算好的。

注意力机制

Transformer 注意力机制核心部分叫做 self-attention (自注意力)。基本流程可以理解为:当前 Token 拿着 Query 去和所有 Key 做匹配,再按匹配权重汇总对应的 Value。 当然这些数据都是向量。可以这么理解:

  • 查询(Q):当前 Token 想找什么信息。
  • 键(K):每个 Token 对外提供的匹配索引。
  • 值(V):真正被加权汇总的信息内容。

实际计算时,同一份输入矩阵 $X$ 会分别乘以三组可训练权重矩阵,得到 $Q$、$K$、$V$ 三组向量。图里把维度压成 4 格只是为了画得清楚;真实模型里,$X \in \mathbb{R}^{L \times d_{model}}$,$W^Q/W^K \in \mathbb{R}^{d_{model} \times d_k}$,$W^V \in \mathbb{R}^{d_{model} \times d_v}$。

图 2:Q/K/V 线性投影。Self-Attention 使用同一份输入 $X$,通过三组不同的权重矩阵生成后续注意力计算需要的 Query、Key 和 Value。

图 3:Scaled Dot-Product Attention(左)与 Multi-Head Attention(右)。左侧展示 Q/K/V 的注意力计算流程,右侧展示 h 个并行注意力头拼接后再线性投影的结构。

对于单个单头的注意力,计算公式如下:

$$\mathrm{Attention}(Q, K, V) = \mathrm{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$

这里的 $d_k$ 是 Key 向量的维度。除以 $\sqrt{d_k}$ 是为了把点积结果拉回更合适的数值范围,避免维度变大后 softmax 太容易进入饱和区。$W^Q$、$W^K$、$W^V$ 这些矩阵也是训练出来的。

$$ \begin{aligned} X &\in \mathbb{R}^{L \times d_{model}} \\ W^Q &\in \mathbb{R}^{d_{model} \times d_k} \\ W^K &\in \mathbb{R}^{d_{model} \times d_k} \\ W^V &\in \mathbb{R}^{d_{model} \times d_v} \end{aligned} $$$$ \begin{aligned} Q &= XW^Q \in \mathbb{R}^{L \times d_k} \\ K &= XW^K \in \mathbb{R}^{L \times d_k} \\ V &= XW^V \in \mathbb{R}^{L \times d_v} \end{aligned} $$

相当于把每个 Token 都投影到 Query、Key、Value 三个表示空间中。

2610-self-attention-qkv-calc.png

然后计算每个 Q 和每个 Key 的相似程度,也表达了每个 Token 对其他 Token 的注意程度,即 $QK^T$。这里的 $\frac{1}{\sqrt{d_k}}$ 是一个缩放,用于控制方差,让点积的方差和输入向量的维度无关。

这个缩放项可以直接推出来。先看某一个 Query 向量 $q$ 和 Key 向量 $k$ 的点积:

$$ s = q \cdot k = \sum_{i=1}^{d_k} q_i k_i $$

为了只看维度带来的影响,先假设这些分量彼此独立,均值为 0,方差为 1。单项乘积的期望为:

$$ \mathbb{E}[q_i k_i] = \mathbb{E}[q_i]\mathbb{E}[k_i] = 0 $$

方差为:

$$ \begin{aligned} \mathrm{Var}(q_i k_i) &= \mathbb{E}[q_i^2 k_i^2] - \mathbb{E}[q_i k_i]^2 \\ &= \mathbb{E}[q_i^2]\mathbb{E}[k_i^2] \\ &= 1 \end{aligned} $$

点积是 $d_k$ 个这样的项相加,所以:

$$ \mathrm{Var}(s) = \mathrm{Var}\left(\sum_{i=1}^{d_k} q_i k_i\right) = d_k $$

也就是说,$d_k$ 越大,$QK^T$ 里的分数波动越大,标准差大约是 $\sqrt{d_k}$。把它除以 $\sqrt{d_k}$ 后:

$$ \mathrm{Var}\left(\frac{s}{\sqrt{d_k}}\right) = \frac{\mathrm{Var}(s)}{d_k} = 1 $$

如果 $q_i$、$k_i$ 的方差不是 1,而是 $\sigma_q^2$、$\sigma_k^2$,点积方差会变成 $d_k\sigma_q^2\sigma_k^2$,这个缩放项同样能消掉由 $d_k$ 带来的那部分放大。

Softmax 对这个尺度很敏感。它的梯度是:

$$ \frac{\partial \mathrm{softmax}(z)_i}{\partial z_j} = \mathrm{softmax}(z)_i \left(\mathbf{1}_{i=j} - \mathrm{softmax}(z)_j\right) $$

如果 $z$ 里的某个分数远大于其他分数,Softmax 会非常接近 one-hot:最大项接近 1,其他项接近 0。这时上面的梯度项也会接近 0,反向传播很难把注意力权重再调回来。在 float 精度有限时,指数函数的差距还可能直接把小概率压到接近 0。除以 $\sqrt{d_k}$ 不是为了改变排序,而是让 logits 的尺度别随着 $d_k$ 增大而失控,减轻 Softmax 饱和和梯度变小的问题。

最后再 Softmax 归一化。

2610-self-attention-qkv.png

这个图里还有一次 Mask 遮挡。它的作用是模拟阅读和写作过程:生成当前位置时,后面的词还没看到,所以不应该参与注意力计算。

$\mathrm{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)$ 的结果是一个 $L \times L$ 的注意力权重矩阵,再乘以 $V$ 后得到 $L \times d_v$ 的输出。现代化LLM有些支持1M上下文,不可能真的 1M * 1M 这么夸张的计算量,肯定是通过稀疏注意力 / 滑动窗口 / 分块注意力或其他方式分块计算的。然后把计算量从 $O(L^2)$ 压到接近 $O(Lw)$,其中 $w$ 是窗口大小。

到这里,单头注意力 的主要流程就结束了。

每个注意力头都有自己的一组 $W^Q, W^K, W^V$,会学到一类匹配偏好,但单头表达能力有限。为了让模型同时看不同关系,就引入了 多头注意力

多头注意力 就是让多个注意力头并行计算,再把结果拼接起来。

$$ \begin{aligned} \operatorname{MultiHead}(X) &= \operatorname{Concat}(\operatorname{head}_1, \dots, \operatorname{head}_h)W^O \\ \text{where } \operatorname{head}_i &= \operatorname{Attention}(XW_i^Q, XW_i^K, XW_i^V) \end{aligned} $$

这里又引入了一个新的投影矩阵:$W^O \in \mathbb{R}^{hd_v \times d_{model}}$,其中 $h$ 是并行的注意力头数(原论文 $h = 8$)。它负责把拼接后的表示重新投影到 $d_{model}$ 维度。

到这里,多头注意力 的计算过程就完成了。当然后面还会经过 残差连接(Residual Connection)和 LayerNorm;再通过 前馈神经网络(FFN) 执行非线性变换。MoE、激活函数、归一化位置这些优化又是另一个层面的内容,已经超出这篇入门笔记的范围,这里先不展开。

《Attention Is All You Need》 论文里的 Position-wise FFN 由两个线性变换组成,中间接一个 ReLU 激活函数:$FFN(x) = \max\left(0, xW_1 + b_1\right)W_2 + b_2$。输入和输出维度为 $d_{model} = 512$,中间层维度为 $d_{ff} = 2048$。

整个模型的注意力整合

我们再回到整体的架构图。

在整体架构里,无论是左边 Encoder 的 self-attention (自注意力) 层,还是右边的 self-attention (自注意力) 层 和 encoder-decoder attention 层,都是多层堆叠出来的。 《Attention Is All You Need》 论文的堆叠层数 $N = 6$。

在左边的 Encoder 的每层堆叠 中,除了第 1 层外,Q、K、V 都来自上一层的输出。

第一层:

$$ H_E^{0} = \operatorname{Dropout}\left(\operatorname{Emb}_{src}(X) + PE_{src}\right) $$

然后对后面每一层:

$$ \begin{aligned} A_E^{l} &= \operatorname{MHA}\left(H_E^{l-1}, H_E^{l-1}, H_E^{l-1}\right) \\ \tilde{H}_E^{l} &= \operatorname{LayerNorm}\left(H_E^{l-1} + \operatorname{Dropout}\left(A_E^{l}\right)\right) \\ F_E^{l} &= \operatorname{FFN}\left(\tilde{H}_E^{l}\right) \\ H_E^{l} &= \operatorname{LayerNorm}\left(\tilde{H}_E^{l} + \operatorname{Dropout}\left(F_E^{l}\right)\right) \end{aligned} \qquad l = 1, \dots, N $$

  • Encoder 的每一层都有自己的 $W^Q,W^K,W^V,W^O$ 和 FFN 参数。
  • Encoder self-attention 里,Q/K/V 都来自同一个输入:第一层来自 Embedding + PE,后续层来自上一层输出。
  • Encoder 不需要 mask,因为输入序列一次性全部可见,每个 token 可以 attend 到所有输入位置。
  • PE: Positional Encoding 不逐层重复添加。位置信息已经混进了 $H^0$,后面通过残差连接、Attention 和 FFN 一层层传下去。

然后:

$$ \begin{aligned} Q_E^l &= H_E^{l-1} W_{E}^{Q,l} \\ K_E^l &= H_E^{l-1} W_{E}^{K,l} \\ V_E^l &= H_E^{l-1} W_{E}^{V,l} \end{aligned} \qquad l = 1,\dots,N $$

右边 Decoder 包含两段注意力计算:Decoder masked self-attentionDecoder encoder-decoder attention。其中:

第一层:

$$ H_D^{0} = \operatorname{Dropout}\left(\operatorname{Emb}_{tgt}(Y_{

然后对后面每一层:$$ \begin{aligned} A_D^{l} &= \operatorname{MaskedMHA}\left(H_D^{l-1}, H_D^{l-1}, H_D^{l-1}\right) \\ \tilde{H}_D^{l} &= \operatorname{LayerNorm}\left(H_D^{l-1} + \operatorname{Dropout}\left(A_D^{l}\right)\right) \\ C_D^{l} &= \operatorname{MHA}\left(\tilde{H}_D^{l}, H_E^{N}, H_E^{N}\right) \\ \hat{H}_D^{l} &= \operatorname{LayerNorm}\left(\tilde{H}_D^{l} + \operatorname{Dropout}\left(C_D^{l}\right)\right) \\ F_D^{l} &= \operatorname{FFN}\left(\hat{H}_D^{l}\right) \\ H_D^{l} &= \operatorname{LayerNorm}\left(\hat{H}_D^{l} + \operatorname{Dropout}\left(F_D^{l}\right)\right) \end{aligned} \qquad l = 1, \dots, N $$

这段公式要拆开看:

  • MaskedMHA:Q/K/V 都来自 Decoder 当前层输入,并且使用 causal mask。
  • Cross-attention MHA:Q 来自 masked self-attention 后的 $\tilde{H}_D^l$,K/V 来自 Encoder 最后一层输出 $H_E^N$。
  • PE 表示 Positional Encoding,MHA 表示多头注意力计算。

然后右边的第一段 Decoder masked self-attention,Q、K、V 都来自 Decoder 自己。

$$ \begin{aligned} Q_{D,\mathrm{self}}^l &= H_D^{l-1} W_{D,\mathrm{self}}^{Q,l} \\ K_{D,\mathrm{self}}^l &= H_D^{l-1} W_{D,\mathrm{self}}^{K,l} \\ V_{D,\mathrm{self}}^l &= H_D^{l-1} W_{D,\mathrm{self}}^{V,l} \end{aligned} \qquad l = 1,\dots,N $$

而右边的第二段 Decoder encoder-decoder attention,也常叫 cross-attention。这里 K、V 来自左边 Encoder 多层堆叠后的最终结果 $H_E^N$,Q 来自 Decoder 当前层 masked self-attention 之后的 $\tilde{H}_D^l$。所以右侧的 Output Embedding 会先进入 Decoder self-attention,再作为 cross-attention 的 Query 来源。

$$ \begin{aligned} Q_{D,\mathrm{cross}}^l &= \tilde{H}_D^l W_{D,\mathrm{cross}}^{Q,l} \\ K_{D,\mathrm{cross}}^l &= H_E^N W_{D,\mathrm{cross}}^{K,l} \\ V_{D,\mathrm{cross}}^l &= H_E^N W_{D,\mathrm{cross}}^{V,l} \end{aligned} \qquad l = 1,\dots,N $$

在语言模型的 Decoder 端,多层 Transformer block 会先得到每个位置的 hidden state;最后通过 lm_head 映射到词表维度,为每个 Token 生成一个 logit,再经过 softmax 得到概率分布。GPT-2 这类模型的 FFN/MLP 中间层常用 GELU,但输出词表 logits 的那一层本质上还是线性投影。

2610-mlp-t-k-to-probability.png

这张图也能解释现在 LLM 里 temperature、top_p / top_k 这些采样参数的作用。 如果是贪心解码,就直接选概率最高的那个词;如果是采样解码,就会在筛选后的概率分布里抽样。

这里也能看出 LLM 的底层目标:在当前上下文里预测下一个 Token 的概率分布。 所以它输出的往往是训练数据、后训练和解码策略共同推出来的高概率结果。这也导致了,如果某些情况下 “真理往往掌握在少数人手中” 的时候,大语言模型就掌握不了真理。

怎么训练 $W^Q$、$W^K$、$W^V$、$W^O$ ?

对于一组注意力矩阵来说,$W^Q$、$W^K$、$W^V$、$W^O$ 是同时训练出来的,具体训练方法和上一章 《大语言模型的基石:Transformer 入坑笔记(二) - 基本原理和 Word Embeddings》 的基本原理一致。主线就是 前向计算 -> loss -> 反向传播 -> 同时更新参数

flowchart TD
    S["训练样本
输入 tokens + 目标 tokens"] --> E["Embedding + Positional Encoding"] subgraph F["前向传播"] E --> P["线性投影
Q = XW^Q
K = XW^K
V = XW^V"] P --> A["Attention
softmax(QK^T / sqrt(d_k))V"] A --> C["多头拼接
Concat(head_1, ..., head_h)"] C --> O["输出投影
Concat(heads) W^O"] O --> N["后续层
Add and Norm / FFN / 输出层"] N --> Y["预测词表概率"] end subgraph L["损失函数"] Y --> CE["Cross Entropy Loss
和真实目标 token 对比"] end subgraph B["反向传播"] CE --> G["计算梯度
dL/dW^Q
dL/dW^K
dL/dW^V
dL/dW^O"] G --> U["优化器更新
Adam / AdamW"] U --> W["同时更新
W^Q, W^K, W^V, W^O"] end W -. "下一批数据继续训练" .-> E

训练样本就是大量的语料文本。更新矩阵参数时,仍然是:

$$ \begin{aligned} W_i^Q &\leftarrow W_i^Q - \eta \frac{\partial \mathcal{L}}{\partial W_i^Q} \\ W_i^K &\leftarrow W_i^K - \eta \frac{\partial \mathcal{L}}{\partial W_i^K} \\ W_i^V &\leftarrow W_i^V - \eta \frac{\partial \mathcal{L}}{\partial W_i^V} \\ W^O &\leftarrow W^O - \eta \frac{\partial \mathcal{L}}{\partial W^O} \end{aligned} \qquad i = 1,\dots,h $$

不同 attention head / layer 的区别主要来自:

  • 结构位置不同:同样叫 attention,看到的输入不一样。比如第 1 层看到的是 $H^0 = Embedding(X) + PE$,第 6 层看到的是前 5 层加工后的结果 $H^5$。
  • 参数初始化不同:只要初始随机矩阵不同,后续训练轨迹就可能分化。
  • 训练过程中梯度不同:每个 head 对 loss 的贡献不同,更新方向也不同。

除了结构位置、初始化和梯度路径,attention head 的差异还会受到 mask、位置编码、head 维度、$W^O$ 的混合方式、残差连接、LayerNorm、Dropout 以及训练数据分布影响。数据类型确实会影响最终学到的模式,比如代码语料多了以后,某些 head 可能对括号、缩进、变量引用更敏感;这些都是同一套参数在反复训练中自然形成的偏好,不是人工设定的。

$W^Q$ 的优化

后面看公开模型配置时,会遇到 q_lora_rank 这个字段。它不是 Transformer 原论文里的标准概念,而是 DeepSeek、Kimi 这类模型在 Attention 投影里使用的低秩结构参数。

普通 Attention 里,Query 通常是一次线性投影:

$$ Q = XW^Q $$

如果输入维度是 $d_{model}$,所有 attention head 拼起来后的 Query 维度是 $h \cdot d_q$,那么:

$$ W^Q \in \mathbb{R}^{d_{model} \times (h \cdot d_q)} $$

q_lora_rank 的实现不会直接用一个完整的大矩阵做 Query 投影,而是先压到一个较小的中间维度,再投影回所有 head 需要的 Query 维度:

$$ C^Q = XW^{DQ} $$$$ Q = C^QW^{UQ} $$

其中:

$$ W^{DQ} \in \mathbb{R}^{d_{model} \times r_q} $$$$ W^{UQ} \in \mathbb{R}^{r_q \times (h \cdot d_q)} $$

这里的 $r_q$ 就是 q_lora_rank。也就是说,它是 Query 投影里的低秩瓶颈维度。

不同模型的 Attention 配置字段不完全一样。以后面表格里的 Kimi-K2.7-Code 为例,hidden_size = 7168q_lora_rank = 1536,64 个 attention head,每个 head 的 Query 维度按 qk_nope_head_dim + qk_rope_head_dim = 128 + 64 = 192 计算,所有 head 拼起来就是:

$$ 64 \times 192 = 12288 $$

如果直接做完整 $W^Q$:

$$ 7168 \times 12288 \approx 88.1M $$

如果拆成 q_lora_rank = 1536 的两段低秩投影:

$$ 7168 \times 1536 + 1536 \times 12288 \approx 29.9M $$

所以 q_lora_rank 的主要作用是减少 Query 投影的参数量和计算规模。

这里的 LoRA 容易和微调里的 LoRA adapter 混淆。常见 LoRA 微调是冻结原始矩阵,再额外训练一个低秩增量:

$$ W^{\prime} = W + AB $$

而这些模型配置里的 q_lora_rank 更像是模型结构本身就把 Query 投影设计成低秩分解:

$$ W^Q \approx W^{DQ}W^{UQ} $$

这两个矩阵从预训练开始就是模型参数的一部分,不是后面外挂上去的微调插件。

混合专家模型(MoE)

原始 Transformer 里,FFN/MLP 层是 dense 的:一个 Token 经过这一层时,会走这层的全部 FFN 参数。MoE 的想法是把 FFN/MLP 换成多组 Expert,再让 Router 为每个 Token 只选其中一小部分。

这个思路很符合直觉。写代码时不太需要心理学专家,做数学题时也不太需要一个擅长社交闲聊的专家;如果每个 Token 都让所有专家参与计算,参数容量是上去了,推理成本也会一起上去。于是就有了 混合专家模型(MoE)

混合专家模型(MoE) 通常是在 Transformer block 里,把 前馈神经网络(FFN/MLP) 从一套 dense MLP 换成多个 Expert。拿到输入 hidden state 后,Router 只会选中一个或几个 Expert 参与计算。

Router 层会根据输入 hidden state 给每个 Expert 打分,再按概率或 top-k 规则选取要激活的 Expert。

最简单的策略可以是只选分数最高的一个 Expert;选更多 Expert 会增加每个 Token 的计算容量,但也会增加推理成本和路由负载。

模型加载时通常仍要加载全部专家,所以内存/显存占用看的是总参数;但每次计算只激活子集,所以实际计算量会小得多。

MoE Router 也有自己的可训练参数,比如:

$$ \begin{aligned} s &= hW^R + b^R \\ p &= \operatorname{softmax}(s) \end{aligned} $$

其中:

  • $h$ 是进入 MoE 层前的 hidden state;
  • $W^R$ 是 Router 的权重矩阵;
  • $s$ 是每个 Expert 的打分;
  • $p$ 是每个 Expert 被选中的概率;
  • 然后取 top-k 个 Expert 参与计算。

专家数量是训练前先定好的,专家“负责什么”通常不是人工指定的,而是在训练过程中自己分化出来的。

混合专家模型(MoE) 还有个参数 moe_intermediate_size,可以理解为每个 MoE Expert 内部 FFN/MLP 的中间层维度。 如果模型用的是 SwiGLU 这类结构,单个 Expert 通常有三组矩阵:

  • gate_proj: $d_{model}$ -> $d_{moe}$
  • up_proj: $d_{model}$ -> $d_{moe}$
  • down_proj: $d_{moe}$ -> $d_{model}$

所以单个 Expert 参数量粗略是:$3 \times d_{model} \times d_{moe}$,也就是 $3 \times hidden\_size \times moe\_intermediate\_size$。

一般来说,在其它条件不变、训练数据和训练预算也足够的前提下,moe_intermediate_size 变大,单个 Expert 的表达能力会更好。 但它不是越大越好,收益会递减,也很容易被训练预算、推理成本和路由质量这些瓶颈卡住。

最后有一个经验可以先记住:总参数量更像模型容量的上限,单次任务效果还要看激活参数、路由质量、训练数据、后训练和解码策略。

大语言模型的参数量参考

一个 decoder-only Transformer 的参数量大致来自这几块:

  • 词表 Embedding:$V \times d_{model}$。如果输出层 lm_head 不和 Embedding 共享权重,再加一份 $V \times d_{model}$。
  • Attention:普通 MHA 可以粗略看成每层 $Q/K/V/O$ 四个投影,约 $4d_{model}^2$。现在很多模型用了 GQA、MLA、DSA、LoRA rank 等变体,实际要按配置和代码算,不能只套这个公式。
  • Dense FFN/MLP:如果是 SwiGLU,常见是 gate_projup_projdown_proj 三个矩阵,约 $3 \times d_{model} \times d_{ff}$。
  • MoE 专家层:每个专家通常也是一套 SwiGLU MLP,单专家约 $3 \times d_{model} \times d_{moe}$。总参数要乘以全部专家数;激活参数只乘以每个 token 实际选中的专家数,再加 shared expert 和 attention 等固定会走的参数。
  • 路由器、Norm、位置编码、MTP、视觉编码器等:通常不是最大头,但开放模型的官方总参数和自己按专家层粗算之间的差额,往往就在这些结构里。

所以 MoE 模型的“总参数”和“激活参数”不是一个概念。总参数是模型权重的完整规模;激活参数是处理单个 token 时实际走过的权重规模。下面的拆分只算权重数量,不算 KV cache、优化器状态,也不把量化后的存储大小混进来。

系列当前公开口径参数结构是否公开备注
GPTOpenAI 模型页当前推荐 GPT-5.5 用于复杂推理和编码 10未公开GPT-4 技术报告明确说没有继续披露 architecture、model size、hardware、training compute 等细节 11。我没有找到 GPT-5.5 的官方参数量、词表、维度、层数。
ClaudeAnthropic 文档当前提到 Claude Fable 5、Claude Mythos 5,并建议复杂任务从 Claude Opus 4.8 起步 12未公开官方文档给的是模型能力、上下文、平台支持等产品信息,没有公开参数量、词表、维度、层数。
GeminiGemini API 模型页当前在 Gemini 3 家族里列出 Gemini 3.1 Pro、Gemini 3.5 Flash 等模型 13未公开官方 API 文档公开的是上下文、输入输出能力、稳定/预览状态,没有公开结构参数。

虽然官方没有给确切的参数文档,但是我们能从网上搜索别人的评测估算或者拿公开 MoE 模型做参照:DeepSeek-V4-Pro 是 1.6T / 49B activated,Kimi-K2.7-Code 是 1T / 32B activated,GLM-5 报告口径是 744B / 40B active。Google 早年的 GLaM 论文也公开过 1.2T 参数的稀疏激活 MoE 模型21。这些模型给了一个大致范围:前沿 MoE 的总参数可以上 T,单 token 激活参数通常是几十 B 到一两百 B 这个量级。

另外还有两条第三方/官方材料参考。第一,Klu 汇总 GPT-4 的公开研究和 SemiAnalysis 等资料,给过一个常被引用的拆法:GPT-4 约 1.8T 总参数,16 个专家,每个约 111B,top-2 激活,再加约 55B shared attention,单次推理约 280B 参数参与22。第二,Gemini 3 Pro 官方模型信息确认它是 sparse MoE Transformer,并明确说这种结构会把总模型容量和每 token 计算/服务成本拆开23。所以下面闭源模型那张表只能看量级作为参考。

系列估算结构总参数量级单 token 激活量级参数组成预估
GPT-5.5以 GPT-4 的 1.8T / 约 280B active 传闻拆法为下界,再考虑两代后的推理/代码能力提升。更像稀疏 MoE + 路由/推理预算组合,而不是单个超大 dense 模型。2T-5T100B-300B专家 MLP 约 85%-95%;attention、shared dense、router、Norm 等约 5%-15%;Embedding + lm_head 通常 1B-5B。多模态 encoder、工具/代码专用头可能另算。
Claude Fable 5 / Mythos 5官方只说 Fable 是最强广泛发布模型、Mythos 是受限可用模型,二者 1M context、128k output、同价。按这个定位,5T 可以作为 MoE 总参数的高位估计。2T-5T80B-250B如果总参数接近 5T,大概率是大量 routed experts 堆出来的 MoE 总量;真正每 token 激活的仍是 top-k 专家 + shared dense/attention。Embedding + lm_head 仍然大概率只是几 B。
Claude Opus 4.8Opus 4.8 价格大约是 Fable/Mythos 的一半,官方定位也是 Opus-tier 而不是最高 Mythos/Fable tier。1T-3T50B-180B比 Fable/Mythos 小一档更合理;如果是 MoE,专家 MLP 是大头;如果是 dense,总参数和激活参数会接近,但 5T dense 对延迟和成本都太重。
Gemini 3.1 Pro / 3.5 FlashGemini 3 Pro 官方确认 sparse MoE;1.5 Pro 报告也说 MoE 让总参数容量和单 token 成本解耦。Pro 和 Flash 很可能不是同一组激活规模。Pro: 1T-3T;Flash: 0.2T-1TPro: 50B-150B;Flash: 10B-60B专家 MLP 约 80%-95%;attention 和长上下文相关模块占剩余主要部分;视觉、音频、视频 encoder 可能是几亿到几十亿参数,并且不一定计入语言模型本体。

开源模型的参数计算就简单多了,可以直接拿 https://huggingface.co 上的模型信息和 config.json 来参考,其中 DeepSeek 和 Kimi 的总参数/激活参数来自模型信息;GLM-5.2 模型信息 18 链接到 GLM-5 技术报告,报告中给出同系 GLM-5 架构为 744B 总参数、40B 激活参数,GLM-5.2 的模型信息没有单独重复一张参数表。

模型官方总量/激活量词表隐藏维度层数AttentionMoE 配置上下文
DeepSeek-V4-Pro1.6T / 49B activated 14129,280 157,16861128 heads,1 KV head,q_lora_rank=1536384 routed experts,6 selected/token,1 shared expert,moe_intermediate_size=30721,048,576
Kimi-K2.7-Code1T / 32B activated,视觉编码器 400M 16163,840(模型信息写 160K)177,16861,其中 1 层 dense64 heads,64 KV heads,q_lora_rank=1536384 routed experts,8 selected/token,1 shared expert,moe_intermediate_size=2048262,144
GLM-5.2GLM-5 技术报告口径为 744B / 40B active 20154,880 196,14478,其中前 3 层 dense64 heads,64 KV heads,q_lora_rank=2048,DSA/IndexShare256 routed experts,8 selected/token,1 shared expert,moe_intermediate_size=20481,048,576

按上面的公式拆一下,会更容易理解这些数字为什么这么大。

模型Embedding + lm_head单专家参数MoE 总专家参数粗算每 token 激活的 MoE 专家参数粗算说明
DeepSeek-V4-Pro$2 \times 129280 \times 7168 \approx 1.85B$$3 \times 7168 \times 3072 \approx 66.1M$$61 \times (384 + 1) \times 66.1M \approx 1.55T$$61 \times (6 + 1) \times 66.1M \approx 28.2B$模型信息给 1.6T / 49B。差额主要来自 attention、路由器、Norm、HCA/CSA/MTP 等固定结构。
Kimi-K2.7-Code$2 \times 163840 \times 7168 \approx 2.35B$$3 \times 7168 \times 2048 \approx 44.0M$$60 \times (384 + 1) \times 44.0M \approx 1.02T$$60 \times (8 + 1) \times 44.0M \approx 23.8B$模型信息写 1T / 32B,属于四舍五入后的公开口径;还有 1 层 dense MLP、attention 和 400M 视觉编码器。
GLM-5.2$2 \times 154880 \times 6144 \approx 1.90B$$3 \times 6144 \times 2048 \approx 37.7M$$75 \times (256 + 1) \times 37.7M \approx 727.6B$$75 \times (8 + 1) \times 37.7M \approx 25.5B$GLM-5 报告给 744B / 40B;除专家层外,还有前 3 层 dense MLP、attention、DSA/IndexShare、MTP 等结构。

可以看到,现代 MoE 大模型的参数大头几乎都在专家 MLP 里,Embedding 反而只是十亿级。

最后

当前基本原理的入门就到这里了。如果真的要做相关业务,还得继续往训练、推理、并行、显存、数据和评测这些细节里钻,这就留到以后再说了。

我本人并非 AI 领域从业者,可能有理解不到位的地方,欢迎指正。