<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Machine Learning on kenji.blog</title><link>http://kenji.blog/zh-cn/categories/machine-learning/</link><description>Recent content in Machine Learning on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 22:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/zh-cn/categories/machine-learning/index.xml" rel="self" type="application/rss+xml"/><item><title>【面向初学者】解读Transformer模型的数学结构</title><link>http://kenji.blog/zh-cn/p/transformer-mathematical-structure/</link><pubDate>Fri, 11 Sep 2026 22:00:00 +0900</pubDate><guid>http://kenji.blog/zh-cn/p/transformer-mathematical-structure/</guid><description>&lt;img src="http://kenji.blog/p/transformer-mathematical-structure/img/eyecatch.jpg" alt="Featured image of post 【面向初学者】解读Transformer模型的数学结构" />&lt;h1 id="引言为什么要学习transformer的数学原理">引言：为什么要学习Transformer的数学原理？
&lt;/h1>&lt;p>毫不夸张地说，“Transformer”架构改写了现代自然语言处理（NLP）乃至整个AI的历史。该模型在2017年由Google的研究人员们在论文《Attention Is All You Need》中首次提出，目前作为OpenAI的GPT系列（ChatGPT的基础技术）、Google的BERT以及Anthropic的Claude等席卷全球的大型语言模型（LLM）的核心组件而发挥着作用。&lt;/p>
&lt;p>然而，目前的情况是，关于Transformer的工作原理，虽然经常能看到诸如“使用Attention（注意力机制）来理解上下文”这样的定性解释，但针对初学者的关于其背后的&lt;strong>数学结构&lt;/strong>的深入解说是出乎意料地少的。为了真正理解AI是如何将“语言”作为“数学公式”来处理，并生成令人惊叹的自然文章的，解读其数学机制是不可或缺的。&lt;/p>
&lt;p>在本文中，将以具备数学和编程基础知识的人（了解高中水平的矩阵和微分概念的人）为对象，彻底且通俗易懂地解开Transformer核心部分的“Self-Attention机制”、“查询·键·值（Q/K/V）模型”、“通过Softmax函数的归一化”以及“Positional Encoding”等数学结构。&lt;/p>
&lt;p>你可能会被一连串的数学公式所压倒，但每一个计算都有其明确的“意义”。当读完这篇文章时，你应该能够理解Transformer并不是什么魔法般的黑盒，而是经过精心设计的数学和统计学的结晶。&lt;/p>
&lt;hr>
&lt;h1 id="1-传统方法的局限性与transformer的创新性">1. 传统方法的局限性与Transformer的创新性
&lt;/h1>&lt;p>在Transformer出现之前，自然语言处理的主流是循环神经网络（RNN）及其派生的LSTM（Long Short-Term Memory）。RNN是为了处理时间序列数据而设计的，它会逐个单词地从开头按顺序读取文章。&lt;/p>
&lt;p>但是，RNN存在两个致命的弱点。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>难以学习长期依赖关系&lt;/strong>：当文章变长时，最开始输入的单词信息在到达最后时会变得淡薄（梯度消失问题）。&lt;/li>
&lt;li>&lt;strong>无法进行并行计算&lt;/strong>：因为必须按顺序处理单词，所以很难利用GPU进行大规模的并行计算，训练需要花费大量的时间。&lt;/li>
&lt;/ol>
&lt;p>Transformer彻底抛弃了RNN的结构，引发了一场只使用“Attention”来捕捉上下文的范式转换。由此，无论序列长度有多长，都不会发生信息丢失，并且能够并行化计算，从而最大限度地发挥GPU的性能。&lt;/p>
&lt;hr>
&lt;h1 id="2-transformer的整体架构">2. Transformer的整体架构
&lt;/h1>&lt;p>首先，让我们俯瞰一下Transformer的整体架构。Transformer大致可以分为“Encoder（编码器）”和“Decoder（解码器）”两个模块。以翻译任务为例，Encoder将输入语言（例如：英语）转换为数学上的向量表示，而Decoder则根据该向量表示生成输出语言（例如：日语）。&lt;/p>
&lt;p>下图简化的展示了Encoder模块的内部结构。&lt;/p>
&lt;div class="mermaid">graph TD
A["输入Token (Input Tokens)"] --> B["输入嵌入 (Input Embedding)"]
B --> C["位置编码 (Positional Encoding)"]
C --> D["多头自注意力机制 (Multi-Head Self-Attention)"]
D --> E["相加与层归一化 (Add &amp; Layer Normalization)"]
E --> F["前馈神经网络 (Feed Forward Network)"]
F --> G["相加与层归一化 (Add &amp; Layer Normalization)"]
G --> H["输出到下一层 (Output to Next Layer)"]
C -.->|"残差连接 (Residual Connection)"| E
E -.->|"残差连接 (Residual Connection)"| G&lt;/div>
&lt;p>从这里开始，让我们按顺序来看看各个组件中进行的数学操作。&lt;/p>
&lt;hr>
&lt;h1 id="3-单词的向量化与位置编码positional-encoding">3. 单词的向量化与位置编码（Positional Encoding）
&lt;/h1>&lt;p>计算机无法直接理解文本。输入的文本首先会被分割成被称为“Token（词元）”的单位，然后每一个Token都会被转换为固定长度的向量。这就是&lt;strong>Input Embedding&lt;/strong>。&lt;/p>
&lt;h2 id="31-input-embedding的数学">3.1 Input Embedding的数学
&lt;/h2>&lt;p>假设词汇表（Vocabulary）的大小为 $V$，嵌入向量的维度数为 $d_{model}$（在原论文中 $d_{model} = 512$）。每个单词 $w_i$ 使用嵌入矩阵 $W_E \in \mathbb{R}^{V \times d_{model}}$ 转换为向量 $x_i \in \mathbb{R}^{d_{model}}$。&lt;/p>
$$ x_i = W_E \cdot \text{one\_hot}(w_i) $$
&lt;p>由此，整篇文章被表示为矩阵 $X \in \mathbb{R}^{N \times d_{model}}$（$N$ 是文章的长度）。&lt;/p>
&lt;h2 id="32-positional-encoding位置编码的必要性与数学公式">3.2 Positional Encoding（位置编码）的必要性与数学公式
&lt;/h2>&lt;p>Transformer并不像RNN那样按顺序处理单词，而是同时并行处理所有单词。从计算速度的角度来看这是一个很大的优势，但同时也会引起&lt;strong>丢失“单词顺序（语序）”这一重要信息&lt;/strong>的问题。例如，“狗咬人”和“人咬狗”，虽然输入的单词集合相同，但含义却完全不同。&lt;/p>
&lt;p>为了将这种语序信息提供给模型，人们设计出了&lt;strong>Positional Encoding&lt;/strong>。
位置 $pos$ 处的单词的第 $i$ 个维度的Positional Encoding $PE$ 是使用以下三角函数来计算的。&lt;/p>
$$ PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$
$$ PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$
&lt;p>这里，$pos$ 是单词的位置（$0, 1, 2, \dots, N-1$），$i$ 是向量维度的索引（$0, 1, \dots, d_{model}/2 - 1$）。&lt;/p>
&lt;h3 id="为什么要使用正弦和余弦">为什么要使用正弦和余弦？
&lt;/h3>&lt;p>乍一看这似乎是非常复杂和奇怪的数学公式，但其中有着深刻的数学原因。通过使用三角函数，模型不仅能够容易地学习到**“绝对位置”&lt;strong>，还能容易地学习到&lt;/strong>“相对位置”的差异**。&lt;/p>
&lt;p>请回想一下高中数学中学到的三角函数加法定理。
&lt;/p>
$$ \sin(\alpha + \beta) = \sin\alpha \cos\beta + \cos\alpha \sin\beta $$
$$ \cos(\alpha + \beta) = \cos\alpha \cos\beta - \sin\alpha \sin\beta $$
&lt;p>距离某个位置 $pos$ 偏移了 $k$ 的位置 $pos + k$ 的Positional Encoding，可以表示为位置 $pos$ 的Positional Encoding的线性组合。也就是说，可以使用矩阵 $M_k$ 将其写成如下形式：&lt;/p>
$$ PE_{pos+k} = M_k \cdot PE_{pos} $$
&lt;p>由此，Attention机制就可以通过内积计算，轻松识别单词之间“相距多远”这种相对距离。此外，通过组合不同波长的多个正弦和余弦波，它还具有这样一个优点：无论文章多长，都能生成唯一的位置向量。&lt;/p>
&lt;p>最终的输入矩阵 $X_{input}$，是将单词的嵌入向量与此位置编码相加而得的结果。&lt;/p>
$$ X_{input} = X + PE $$
&lt;hr>
&lt;h1 id="4-self-attention自注意力机制的深渊数学">4. Self-Attention（自注意力机制）的深渊数学
&lt;/h1>&lt;p>终于我们要踏入Transformer最重要的组件——**Self-Attention（自注意力机制）**了。Self-Attention的目的是“计算文章中所有单词彼此之间的相关度，并将每个单词的向量更新为考虑到上下文的更丰富的表示”。&lt;/p>
&lt;p>在这里，使用了“搜索系统”的类比。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Query (Q)&lt;/strong>: 查询（搜索词）。“我现在正在寻找的信息是什么？”&lt;/li>
&lt;li>&lt;strong>Key (K)&lt;/strong>: 键（标题）。“我所拥有的信息是什么？”&lt;/li>
&lt;li>&lt;strong>Value (V)&lt;/strong>: 值（实体）。“我实际提供的信息是什么？”&lt;/li>
&lt;/ul>
&lt;h2 id="41-矩阵-q-k-v-的生成">4.1 矩阵 $Q, K, V$ 的生成
&lt;/h2>&lt;p>对于输入矩阵 $X \in \mathbb{R}^{N \times d_{model}}$（为了简化说明，这里忽略批次大小），通过将其与可学习的权重矩阵 $W^Q, W^K, W^V \in \mathbb{R}^{d_{model} \times d_k}$ 相乘，来计算查询 $Q$、键 $K$、值 $V$。（通常 $d_k = d_v = d_{model} / h$）&lt;/p>
$$ Q = X W^Q $$
$$ K = X W^K $$
$$ V = X W^V $$
&lt;p>这里，$Q, K, V$ 全都是 $\mathbb{R}^{N \times d_k}$ 的矩阵。&lt;/p>
&lt;h2 id="42-attention-score的计算内积">4.2 Attention Score的计算（内积）
&lt;/h2>&lt;p>为了衡量每个单词的Query与其他所有单词的Key之间有多大程度的关联，需要计算向量的&lt;strong>内积&lt;/strong>。用矩阵运算来写的话如下所示：&lt;/p>
$$ \text{Scores} = Q K^T $$
&lt;p>通过该计算得到的矩阵 $\text{Scores} \in \mathbb{R}^{N \times N}$ 的每个元素 $s_{ij}$，表示第 $i$ 个单词的Query与第 $j$ 个单词的Key的内积，即“相关度的强度”。&lt;/p>
&lt;h2 id="43-缩放scale">4.3 缩放（Scale）
&lt;/h2>&lt;p>通过内积计算得分存在一个问题。当向量的维度 $d_k$ 变大时，内积的值会变得极端地大或极端地小。&lt;/p>
&lt;p>让我们从数学上证明这一点。
假设查询的每个元素 $q \sim \mathcal{N}(0, 1)$、键的每个元素 $k \sim \mathcal{N}(0, 1)$ 服从独立的标准正态分布。
求内积 $q \cdot k = \sum_{i=1}^{d_k} q_i k_i$ 的均值和方差。
均值：因为 $\mathbb{E}[q_i k_i] = \mathbb{E}[q_i] \mathbb{E}[k_i] = 0 \times 0 = 0$，所以总和的均值也是 $0$。
方差：由于独立性，$q_i k_i$ 的方差为 $\text{Var}(q_i k_i) = \mathbb{E}[(q_i k_i)^2] - (\mathbb{E}[q_i k_i])^2 = 1 \times 1 - 0 = 1$。
因此，整个内积的方差等于维度数 $d_k$。&lt;/p>
$$ \text{Var}(q \cdot k) = d_k $$
&lt;p>当方差变大时，在之后应用的Softmax函数中，除最大值以外的梯度会变得极其小，发生“梯度消失”，导致学习无法进行。
为了防止这种情况，将得分除以 $\sqrt{d_k}$（进行缩放），从而使方差始终保持在 $1$。&lt;/p>
$$ \text{Scaled Scores} = \frac{Q K^T}{\sqrt{d_k}} $$
&lt;h2 id="44-通过softmax函数实现概率化">4.4 通过Softmax函数实现概率化
&lt;/h2>&lt;p>为了将得到的得分转换为总和为 $1$ 的概率分布（权重），对每一行应用 &lt;strong>Softmax函数&lt;/strong>。&lt;/p>
$$ a_{ij} = \text{softmax}(s_i)_j = \frac{\exp(s_{ij} / \sqrt{d_k})}{\sum_{m=1}^N \exp(s_{im} / \sqrt{d_k})} $$
&lt;p>矩阵 $A \in \mathbb{R}^{N \times N}$ 被称为Attention Weight（注意力权重）矩阵。查看该矩阵的每一行 $i$，它用0到1的值表示了“在理解单词 $i$ 时，应该对其他哪个单词 $j$ 给予多大的关注（Attention）”。&lt;/p>
&lt;h2 id="45-value的加权和">4.5 Value的加权和
&lt;/h2>&lt;p>最后，使用得到的Attention Weight矩阵 $A$，计算Value矩阵 $V$ 的加权和。&lt;/p>
$$ \text{Output} = A V = \text{softmax}\left(\frac{Q K^T}{\sqrt{d_k}}\right) V $$
&lt;p>通过该运算输出的矩阵 $Z \in \mathbb{R}^{N \times d_v}$，就是“考虑到上下文而更新的单词向量表示”的集合。
这就是论文中所定义的 &lt;strong>Scaled Dot-Product Attention&lt;/strong> 的全貌。&lt;/p>
&lt;hr>
&lt;h1 id="5-multi-head-attention多头注意力机制">5. Multi-Head Attention（多头注意力机制）
&lt;/h1>&lt;p>如果只进行一次Attention计算（单头），可能只能捕捉到一个视角（例如“语法关系”）的上下文。因此，为了同时捕捉语言所具有的多种语义和句法关系（如“主语和谓语”、“代词及其指代对象”等），引入了 &lt;strong>Multi-Head Attention&lt;/strong>。&lt;/p>
&lt;p>将前面的 $Q, K, V$ 的生成与Attention计算并行进行 $h$ 次（头的数量。在原论文中 $h=8$）。&lt;/p>
$$ \text{head}_i = \text{Attention}(X W_i^Q, X W_i^K, X W_i^V) $$
&lt;p>这里，$W_i^Q, W_i^K, W_i^V \in \mathbb{R}^{d_{model} \times d_k}$ 是第 $i$ 个头专用的可学习的权重矩阵。&lt;/p>
&lt;p>从各个头输出的结果 $\text{head}_i \in \mathbb{R}^{N \times d_v}$ 在横向上进行拼接（Concatenate）。&lt;/p>
$$ \text{Concat}(\text{head}_1, \dots, \text{head}_h) \in \mathbb{R}^{N \times (h \cdot d_v)} $$
&lt;p>通常设定使得 $h \cdot d_v = d_{model}$，因此拼接后的维度将再次恢复为与输入相同的 $d_{model}$。最后，将此矩阵乘以权重矩阵 $W^O \in \mathbb{R}^{d_{model} \times d_{model}}$，从而得到最终的输出。&lt;/p>
$$ \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h) W^O $$
&lt;div class="mermaid">graph TD
X["输入 X (Input X)"] --> Q1["Q1"]
X --> K1["K1"]
X --> V1["V1"]
Q1 &amp; K1 &amp; V1 --> H1["头 1 (Head 1)"]
X --> Q2["Q2"]
X --> K2["K2"]
X --> V2["V2"]
Q2 &amp; K2 &amp; V2 --> H2["头 2 (Head 2)"]
X --> QN["..."]
X --> KN["..."]
X --> VN["..."]
QN &amp; KN &amp; VN --> HN["头 h (Head h)"]
H1 &amp; H2 &amp; HN --> C["拼接 (Concatenate)"]
C --> WO["乘以 WO (Multiply by WO)"]
WO --> OUT["多头输出 (Multi-Head Output)"]&lt;/div>
&lt;hr>
&lt;h1 id="6-feed-forward-neural-network-ffn">6. Feed-Forward Neural Network (FFN)
&lt;/h1>&lt;p>Multi-Head Attention的输出接下来将被输入到 &lt;strong>Position-wise Feed-Forward Network (FFN)&lt;/strong> 中。
这是一个应用于序列中“每个位置（单词）独立”的两层全连接神经网络。&lt;/p>
&lt;p>用数学公式表示如下：&lt;/p>
$$ \text{FFN}(x) = \max(0, x W_1 + b_1) W_2 + b_2 $$
&lt;p>这里，$\max(0, z)$ 表示ReLU（Rectified Linear Unit）激活函数（在最近的模型中也经常使用GELU或SwiGLU）。&lt;/p>
&lt;p>这个网络的作用非常重要。Attention机制学习的是“单词之间的关系（空间・序列关系）”，而FFN负责的是“每个单词向量本身的非线性特征变换”。
通常，通过第一层的权重 $W_1$ 暂时将维度大幅扩大（例如从 $d_{model}=512$ 扩大到4倍的 $d_{ff}=2048$），在特征空间上进行复杂的计算后，再通过第二层的权重 $W_2$ 恢复到原来的维度。这种“维度的扩大与缩小”使模型的表达能力得到了飞跃性的提升。&lt;/p>
&lt;hr>
&lt;h1 id="7-残差连接residual-connection与层归一化layer-normalization">7. 残差连接（Residual Connection）与层归一化（Layer Normalization）
&lt;/h1>&lt;p>在深度学习中，如果加深网络的层数，在训练时就会出现梯度消失或爆炸的问题，导致无法很好地学习。为了防止这种情况发生，在Transformer的每个子层（Attention和FFN）周围，都配置了 &lt;strong>残差连接（Residual Connection）&lt;/strong> 和 &lt;strong>层归一化（Layer Normalization）&lt;/strong>。&lt;/p>
&lt;p>用数学公式来写，子层的输出将被如下处理：&lt;/p>
$$ \text{Output} = \text{LayerNorm}(x + \text{Sublayer}(x)) $$
&lt;h2 id="71-残差连接-x--textsublayerx">7.1 残差连接 ($x + \text{Sublayer}(x)$)
&lt;/h2>&lt;p>将输入 $x$ 直接加到子层的输出上。这样一来，在反向传播时梯度会通过捷径直接传递到较浅的层，因此即使层数加深，训练也能保持稳定。&lt;/p>
&lt;h2 id="72-layer-normalization的数学">7.2 Layer Normalization的数学
&lt;/h2>&lt;p>Layer Normalization是计算特征维度方向上的均值和方差，并对数据进行归一化的技术。在批次大小 $B$、序列长度 $N$、维度数 $d_{model}$ 的输入中，对某一个单词向量 $x \in \mathbb{R}^{d_{model}}$ 进行归一化。&lt;/p>
&lt;p>计算均值 $\mu$ 和方差 $\sigma^2$。
&lt;/p>
$$ \mu = \frac{1}{d_{model}} \sum_{i=1}^{d_{model}} x_i $$
$$ \sigma^2 = \frac{1}{d_{model}} \sum_{i=1}^{d_{model}} (x_i - \mu)^2 $$
&lt;p>然后，得到归一化后的输出 $\hat{x}$。
&lt;/p>
$$ \text{LN}(x) = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} \odot \gamma + \beta $$
&lt;p>
（$\epsilon$ 是防止除以零的极小常数。$\gamma, \beta$ 是可学习的缩放和平移参数）&lt;/p>
&lt;p>之所以采用层方向的归一化（Layer Normalization）而不是批次方向的归一化（Batch Normalization），是因为在处理像文章这样长度不固定的序列数据时，批次间的统计量容易变得不稳定。通过Layer Normalization，Transformer可以实现不依赖于批次大小的稳定学习。&lt;/p>
&lt;hr>
&lt;h1 id="8-解码器特有的结构masked-attention与cross-attention">8. 解码器特有的结构：Masked Attention与Cross-Attention
&lt;/h1>&lt;p>到目前为止讲解的结构都是编码器的。在生成文章的解码器模块中，结构稍有不同。&lt;/p>
&lt;h2 id="81-masked-multi-head-attention">8.1 Masked Multi-Head Attention
&lt;/h2>&lt;p>解码器的作用是“根据过去的单词预测下一个单词”。因此，如果在训练时看到了“未来的单词”，那就等于作弊了。为了防止这种情况发生的数学操作就是 &lt;strong>Masking（掩码）&lt;/strong>。&lt;/p>
&lt;p>对于得分矩阵 $Q K^T$，将其加上一个在上三角部分（相当于未来信息）设置为接近 $-\infty$ 的极小值的掩码矩阵 $M$。&lt;/p>
$$ M_{ij} = \begin{cases} 0 &amp; (i \le j) \\ -\infty &amp; (i > j) \end{cases} $$
$$ \text{Masked Attention}(Q, K, V) = \text{softmax}\left(\frac{Q K^T + M}{\sqrt{d_k}}\right) V $$
&lt;p>在计算Softmax函数时，因为 $\exp(-\infty) = 0$，所以对未来单词的Attention Weight将完全变为 $0$。由此，就可以实现保留了因果关系（Causality）的自回归生成。&lt;/p>
&lt;h2 id="82-encoder-decoder-cross-attention">8.2 Encoder-Decoder Cross-Attention
&lt;/h2>&lt;p>解码器的第二个子层是参考编码器输出的 &lt;strong>Cross-Attention&lt;/strong>。
在这里，$Q$ 是从前一个解码器层生成的，而 $K$ 和 $V$ 则是从编码器的最后一层的输出中生成的。&lt;/p>
$$ Q_{decoder} = X_{dec} W^Q $$
$$ K_{encoder} = X_{enc} W^K $$
$$ V_{encoder} = X_{enc} W^V $$
&lt;p>通过这个计算，在翻译等任务中，模型就能学习到“现在正在翻译的单词，与原外语文章的哪个部分有着很强的关联”。&lt;/p>
&lt;hr>
&lt;h1 id="9-计算复杂度与现代优化的数学">9. 计算复杂度与现代优化的数学
&lt;/h1>&lt;p>Transformer是一个出色的模型，但也存在源于其数学结构的“弱点”。
请关注Self-Attention的计算复杂度。在计算得分矩阵 $Q K^T$ 时，因为要将 $(N \times d_k)$ 的矩阵与 $(d_k \times N)$ 的矩阵相乘，所以其计算复杂度为 &lt;strong>$O(N^2 \cdot d_{model})$&lt;/strong>。&lt;/p>
&lt;p>也就是说，&lt;strong>相对于序列长度 $N$，计算复杂度和内存使用量呈平方级增长&lt;/strong>。
如果文章很短则不成问题，但如果想将像一整本书这样超长的上下文输入到LLM中，$N$ 就会达到数万至数十万，在传统的Attention计算下GPU的内存会瞬间耗尽。&lt;/p>
&lt;p>为了打破这种 $O(N^2)$ 的诅咒，近年来从数学和硬件方法的角度提出了各种各样的优化方案。
其中具有代表性的例子就是 &lt;strong>FlashAttention&lt;/strong>。FlashAttention是一种为了将GPU的内存层级（SRAM和HBM）之间的数据传输（内存访问）降至最低，将Attention计算分割成瓦片状（Tiling）来执行的算法。尽管在数学公式上它输出的是与标准Attention完全相同的结果（Exact Attention），但通过硬件级别的优化实现了惊人的速度提升和内存削减，使得GPT-4等长上下文模型的实现成为了可能。&lt;/p>
&lt;p>除此之外，将计算复杂度近似为 $O(N \log N)$ 或 $O(N)$ 的 Sparse Attention 和 Linear Attention 等研究也正在积极进行中。&lt;/p>
&lt;hr>
&lt;h1 id="10-实现的印象类似pytorch的伪代码">10. 实现的印象（类似PyTorch的伪代码）
&lt;/h1>&lt;p>如果将到目前为止的数学结构落实到实际的编程代码（Python / PyTorch）中，你会发现它可以用令人惊讶的简单方式编写出来。下面展示了Self-Attention核心部分的伪代码。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">torch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">torch.nn.functional&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">F&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">math&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">scaled_dot_product_attention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mask&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># q, k, v的形状: [batch_size, num_heads, seq_length, d_k]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">d_k&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">q&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 1. 通过内积计算得分: Q * K^T&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 转置最后两个维度以计算矩阵乘积&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">matmul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">transpose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 2. 缩放&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">scores&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">math&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sqrt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">d_k&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 3. 掩码 (适用于Masked Attention的情况)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">mask&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">scores&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">masked_fill&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mask&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mf">1e9&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 4. 通过Softmax实现概率化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">attention_weights&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">softmax&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">scores&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dim&lt;/span>&lt;span class="o">=-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 5. 乘以Value矩阵&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">matmul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">attention_weights&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">output&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">attention_weights&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>可以直观地看到，用数学公式表达的 $Q K^T / \sqrt{d_k}$ 被直接实现为了 &lt;code>torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k)&lt;/code>。借助于高级优化库的力量，只需几行代码就能实现数学理论，这是深度学习非常有趣的一面。&lt;/p>
&lt;hr>
&lt;h1 id="结语从数学公式中看到的智能的形状">结语：从数学公式中看到的“智能”的形状
&lt;/h1>&lt;p>在本文中，我们解开了Transformer模型深处的数学结构。&lt;/p>
&lt;p>将单词映射到多维向量空间的Embedding、通过合成三角波来表示位置信息的Positional Encoding、以及源于信息检索类比的矩阵内积计算——Self-Attention机制。这每一个组件，不过是线性代数、微积分、概率统计等基础数学知识的累积罢了。&lt;/p>
&lt;p>然而，当这些简单的矩阵运算重叠了无数层，通过几十亿、几千亿的参数从庞大的数据集中学习模式时，在那之中就浮现出了仿佛能理解我们的“语言”、进行逻辑推理、时而还能产生创造性想法的“智能的形状”。&lt;/p>
&lt;p>正如“Attention Is All You Need”这个带有挑衅性的标题所示，彻底抛弃复杂的循环处理和卷积处理，专注于纯粹的“Attention（相关度）”计算，这种架构的美，正是在于其数学上的简洁性。&lt;/p>
&lt;p>未来，也许会出现超越Transformer的全新架构（例如State Space Model的Mamba等），但Transformer所建立的“通过Attention理解上下文”的数学框架，必将被永远铭刻在AI的历史中。&lt;/p>
&lt;p>如果你今后有机会使用ChatGPT或Claude等LLM，请想象一下在它们的后台中，每秒正进行着数万亿次 $Q K^T$ 的矩阵乘法运算，以及Softmax函数正在计算着概率的情景。这样你对技术的清晰度会提升，也一定能感受到AI的世界更加有趣。&lt;/p>
&lt;h3 id="参考文献">参考文献
&lt;/h3>&lt;ul>
&lt;li>Vaswani, A., et al. (2017). &amp;ldquo;Attention Is All You Need.&amp;rdquo; &lt;em>Advances in Neural Information Processing Systems&lt;/em>.&lt;/li>
&lt;li>Alammar, J. (2018). &amp;ldquo;The Illustrated Transformer.&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>&lt;em>这篇文章是为学习自然语言处理和AI的数学基础的人们撰写的指南。如果有任何疑问或讨论，请务必在评论区告诉我！&lt;/em>&lt;/p></description></item><item><title>解析llama.cpp的量化技术（GGUF）原理</title><link>http://kenji.blog/zh-cn/p/llama-cpp-quantization-gguf/</link><pubDate>Fri, 11 Sep 2026 00:00:00 +0900</pubDate><guid>http://kenji.blog/zh-cn/p/llama-cpp-quantization-gguf/</guid><description>&lt;img src="http://kenji.blog/p/llama-cpp-quantization-gguf/img/eyecatch.jpg" alt="Featured image of post 解析llama.cpp的量化技术（GGUF）原理" />&lt;h2 id="1-引言为什么llm需要量化">1. 引言：为什么LLM需要量化？
&lt;/h2>&lt;p>近年来，大规模语言模型（LLM: Large Language Models）的进化非常显著，但在其背后，“计算资源枯竭”和“内存带宽瓶颈”这两个严重的问题浮出水面。例如，如果将 Llama 3 这样具有 70B（700亿）参数的模型以标准的 16位浮点数（FP16）加载到内存中，仅参数就会消耗约 140GB 的 VRAM/RAM。如果再加上推理时的上下文（KV缓存），除非将多台面向数据中心的高端GPU（NVIDIA A100 80GB 或 H100 80GB）进行集群，否则无法运行。&lt;/p>
&lt;p>为了让个人开发者和边缘设备（MacBook或一般的游戏PC）也能运行LLM，&lt;strong>llama.cpp&lt;/strong> 及其核心的 &lt;strong>量化（Quantization）技术&lt;/strong> 作为救世主应运而生。特别是名为 &lt;strong>GGUF (GPT-Generated Unified Format)&lt;/strong> 的文件格式以及被称为 &lt;strong>k-quants&lt;/strong> 的高级块级量化算法，这是一种在极力抑制模型精度（Perplexity）下降的同时，将模型大小压缩到几分之一的突破性方法。&lt;/p>
&lt;p>本文将从 llama.cpp 中量化的数学背景开始，彻底解析它与 GGML 格式的区别、GGUF 格式的详细结构，以及 k-quants 的内部机制。&lt;/p>
&lt;hr>
&lt;h2 id="2-量化quantization的数学基础">2. 量化（Quantization）的数学基础
&lt;/h2>&lt;p>在 LLM 的语境中，量化是指将连续的值（或高精度的浮点数）映射为位数更少（INT8、INT4、INT3 等）的离散值的操作。&lt;/p>
&lt;h3 id="21-线性量化的基本公式">2.1. 线性量化的基本公式
&lt;/h3>&lt;p>最简单的方法是线性量化（Min-Max量化）。假设原始的高精度权重张量为 $W$，量化后的整数张量为 $W_q$。&lt;/p>
$$ W_q = \text{round}\left( \frac{W}{S} \right) + Z $$
&lt;p>这里，&lt;/p>
&lt;ul>
&lt;li>$S$ 是 &lt;strong>缩放因子（Scale Factor）&lt;/strong>，决定了量化的步长（分辨率）。&lt;/li>
&lt;li>$Z$ 是 &lt;strong>零点（Zero-point）&lt;/strong>，它是一个偏置值，用于平移实数 $0.0$ 在量化后对应的整数值。&lt;/li>
&lt;li>$\text{round}(\cdot)$ 是向最近整数取整的函数。&lt;/li>
&lt;/ul>
&lt;p>通过反量化（Dequantization），在推理时还原出近似的实数权重 $\tilde{W}$。&lt;/p>
$$ \tilde{W} = S \times (W_q - Z) $$
&lt;h3 id="22-对称量化-vs-非对称量化">2.2. 对称量化 vs 非对称量化
&lt;/h3>&lt;p>根据零点 $Z$ 的处理方式，主要分为两种方式。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>非对称量化 (Asymmetric Quantization)&lt;/strong>
使用数据的最小值 $W_{\min}$ 和最大值 $W_{\max}$ 进行映射。
&lt;/p>
$$ S = \frac{W_{\max} - W_{\min}}{2^b - 1}, \quad Z = \text{round}\left(-\frac{W_{\min}}{S}\right) $$
&lt;p>
其中 $b$ 是量化位数（例如：4位的话 $2^4-1 = 15$）。由于需要保留 $Z$，计算和内存的开销会略微增加。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>对称量化 (Symmetric Quantization)&lt;/strong>
使用数据绝对值的最大值，以零为中心进行映射（$Z=0$）。
&lt;/p>
$$ S = \frac{\max(|W_{\max}|, |W_{\min}|)}{2^{b-1} - 1}, \quad Z = 0 $$
&lt;p>
llama.cpp 早期的量化（例如传统的 Q4_0 等）采用了对称量化，由于没有 $Z$ 项，其优点是使用 SIMD 指令进行内积计算时速度非常快。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="3-从-ggml-到-gguf-的进化与文件结构">3. 从 GGML 到 GGUF 的进化与文件结构
&lt;/h2>&lt;p>谈到 llama.cpp，就不得不提用 C++ 编写的张量运算库 &lt;strong>GGML&lt;/strong>，以及由此派生出的文件格式 &lt;strong>GGUF&lt;/strong>。&lt;/p>
&lt;h3 id="31-ggml的挑战">3.1. GGML的挑战
&lt;/h3>&lt;p>早期的 llama.cpp 使用 &lt;code>ggml&lt;/code> 格式（以及 &lt;code>ggjt&lt;/code> 等变体）。然而，这些格式存在以下问题：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>缺乏可扩展性：&lt;/strong> 魔数和超参数以固定长度、固定顺序硬编码在内，每次添加新的模型架构（如 Llama, Falcon, Mixtral 等）或新的分词器时，都会产生破坏性更改。&lt;/li>
&lt;li>&lt;strong>丧失向后兼容性：&lt;/strong> 格式更新频繁，导致旧的模型文件经常无法被最新版本的 llama.cpp 读取。&lt;/li>
&lt;/ul>
&lt;h3 id="32-gguf格式的诞生">3.2. GGUF格式的诞生
&lt;/h3>&lt;p>2023年8月引入的 &lt;strong>GGUF&lt;/strong> 是一种为了解决这些问题而设计的高通用性格式。其最大的特点是采用了 &lt;strong>基于键值对（Key-Value）的元数据结构&lt;/strong>。&lt;/p>
&lt;p>下面的 Mermaid 图抽象展示了 GGUF 的文件结构。&lt;/p>
&lt;div class="mermaid">graph TD
A["GGUF文件"] --> B["头部 (魔数, 版本号)"]
A --> C["元数据 (键值对)"]
A --> D["张量信息 (名称, 形状, 偏移量)"]
A --> E["张量数据 (二进制负载)"]
C --> C1["general.architecture: llama"]
C --> C2["llama.context_length: 4096"]
C --> C3["tokenizer.ggml.tokens: [...]"]
E --> E1["第0层权重"]
E --> E2["第1层权重"]
E --> E3["..."]&lt;/div>
&lt;p>&lt;strong>GGUF的主要优点：&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>灵活性：&lt;/strong> 将模型的超参数、RoPE（Rotary Positional Embedding）设置、分词器的词汇数据等全部作为命名的键值对存储。未知的键会被忽略，因此很容易添加新功能。&lt;/li>
&lt;li>&lt;strong>端序无关：&lt;/strong> GGUF 默认采用小端序，但由于带有显式的标志，因此在不同架构之间也具有安全的便携性。&lt;/li>
&lt;li>&lt;strong>针对 mmap (内存映射) 优化：&lt;/strong> 张量数据在特定的边界上进行了对齐（填充），可以使用操作系统的 &lt;code>mmap()&lt;/code> 系统调用直接从磁盘映射到内存空间。这使得模型加载的初始化时间几乎为零。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="4-k-quants-的深渊高级的块级量化">4. k-quants 的深渊：高级的块级量化
&lt;/h2>&lt;p>GGUF 格式的精髓在于负责压缩模型权重的 &lt;strong>k-quants (K-quantization)&lt;/strong> 机制。&lt;/p>
&lt;p>通常神经网络的权重，从整个层来看接近正态分布，但在局部存在异常值（Outliers）。如果用统一的缩放因子 $S$ 对整个层的权重进行量化，就会被异常值拖累，导致小权重的信息完全丢失。&lt;/p>
&lt;p>为了防止这种情况，llama.cpp 进行了 &lt;strong>块级量化（Block-wise Quantization）&lt;/strong>。将权重张量分割成小块（例如 32 个元素或 256 个元素），并让每个块拥有自己独有的缩放因子（和零点）。&lt;/p>
&lt;h3 id="41-传统量化q4_0-q4_1的局限性">4.1. 传统量化（Q4_0, Q4_1）的局限性
&lt;/h3>&lt;p>早期的 &lt;code>Q4_0&lt;/code> 将 32 个 FP16 权重作为一个块，并共享 1 个 FP16 缩放因子。&lt;/p>
&lt;ul>
&lt;li>块大小: 32&lt;/li>
&lt;li>内存: 1个缩放(16bit) + 32个4bit权重(128bit) = 144bit&lt;/li>
&lt;li>每个元素的有效位数 (bpw: bits per weight): $144 / 32 = 4.5$ bpw&lt;/li>
&lt;/ul>
&lt;p>这已经足够优秀，但也看到了精度和压缩率的极限。于是，具有更复杂、更精细层次结构的 &lt;strong>k-quants&lt;/strong> 登场了。&lt;/p>
&lt;h3 id="42-超级块与子块的层次结构以-q4_k_m-为例">4.2. 超级块与子块的层次结构（以 Q4_K_M 为例）
&lt;/h3>&lt;p>k-quants 拥有一个层次结构：大的“超级块（Super-block）”和包含在其中的小的“子块（Sub-block）”。通过这种方式，对元数据（缩放值等）本身也进行了量化，在极限降低 bpw 的同时保持精度。&lt;/p>
&lt;p>让我们来看看最受欢迎的设置 &lt;strong>Q4_K_M&lt;/strong> 的结构。Q4_K_M 使用 256 个元素的超级块。&lt;/p>
&lt;div class="mermaid">graph TD
A["超级块 (256个权重)"] --> B["缩放元数据 (FP16/INT8)"]
A --> C["子块 0 (32个权重, 4-bit)"]
A --> D["子块 1 (32个权重, 4-bit)"]
A --> E["..."]
A --> F["子块 7 (32个权重, 4-bit)"]
B --> B1["超级缩放因子 (FP16)"]
B --> B2["子缩放因子 (8 x 6-bit)"]
B --> B3["子最小值 (8 x 6-bit)"]&lt;/div>
&lt;p>在 C++（GGML）中，实际的结构体定义如下。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// llama.cpp 中 block_q4_K 的概念结构
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="cp">#define QK_K 256
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">struct&lt;/span> &lt;span class="nc">block_q4_K&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">d&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// 整个超级块的超级缩放因子 (如 FP16 x 2)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">scales&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">12&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// 8个子块(每块32元素)的 6-bit 缩放因子和 6-bit 最小值(零点)打包后的数据
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">qs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">QK_K&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// 4-bit 量化后的权重数据 (256元素 / 2 = 128 字节)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>数学反量化（Dequantization）处理：&lt;/strong>&lt;/p>
&lt;p>子块 $i$（$0 \le i &lt; 8$）内的元素 $j$（$0 \le j &lt; 32$）的近似实数值 $\tilde{W}_{i, j}$ 的计算方法如下：&lt;/p>
$$ \tilde{W}_{i, j} = S_{\text{super}} \times s_i \times (w_{i, j} - m_i) $$
&lt;ul>
&lt;li>$S_{\text{super}}$: 整个超级块的浮点数缩放因子&lt;/li>
&lt;li>$s_i$: 为子块 $i$ 量化的 6-bit 缩放因子&lt;/li>
&lt;li>$m_i$: 为子块 $i$ 量化的 6-bit 最小值（零点）&lt;/li>
&lt;li>$w_{i, j}$: 4-bit 的量化权重 ($0 \dots 15$)&lt;/li>
&lt;/ul>
&lt;p>通过这种层次结构，在保持对异常值适应能力的同时，极大地减少了缩放因子本身所占用的内存量。Q4_K_M 整体实现了约 &lt;strong>4.8 bpw&lt;/strong>。&lt;/p>
&lt;h3 id="43-多样化的-k-quants-选项">4.3. 多样化的 k-quants 选项
&lt;/h3>&lt;p>llama.cpp 根据不同目的提供了多种变体。“K” 后面的后缀（S, M, L）代表大小。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">格式&lt;/th>
&lt;th style="text-align:center">BPW (Bits per Weight)&lt;/th>
&lt;th style="text-align:left">概要与特征&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q2_K&lt;/strong>&lt;/td>
&lt;td style="text-align:center">2.5～3.3&lt;/td>
&lt;td style="text-align:left">极限压缩。精度下降显著，适用于 VRAM 极少的环境。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q3_K_M&lt;/strong>&lt;/td>
&lt;td style="text-align:center">3.3&lt;/td>
&lt;td style="text-align:left">3位量化的标准。比 Q4 退化，但通常在可接受范围内。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q4_K_M&lt;/strong>&lt;/td>
&lt;td style="text-align:center">4.8&lt;/td>
&lt;td style="text-align:left">&lt;strong>推荐的黄金分割点&lt;/strong>。兼顾模型大小减半和精度维持。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q5_K_M&lt;/strong>&lt;/td>
&lt;td style="text-align:center">5.5&lt;/td>
&lt;td style="text-align:left">要求更高精度时使用。介于 Q4 和 FP16 之间的定位。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q6_K&lt;/strong>&lt;/td>
&lt;td style="text-align:center">6.6&lt;/td>
&lt;td style="text-align:left">维持与 FP16 几乎同等的 Perplexity，但文件体积较大。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q8_0&lt;/strong>&lt;/td>
&lt;td style="text-align:center">8.5&lt;/td>
&lt;td style="text-align:left">相当于 INT8。主要用于推理时的计算用中间张量，或仅在最后一层使用。&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>※ 实际的 BPW 会根据模型的张量（例如 Attention 的 Q/K/V 投影，还是 FFN 的权重），进行混合量化（Mixed Quantization），从而在整个模型中求平均。内部会进行优化，例如将重要的张量用 Q6 量化，其他的用 Q4 量化。&lt;/p>
&lt;hr>
&lt;h2 id="5-推理时的性能优化simd-与-cuda-架构">5. 推理时的性能优化：SIMD 与 CUDA 架构
&lt;/h2>&lt;p>仅仅将 GGUF 模型加载到内存中，推理并不会变快。LLM 推理的大部分是“矩阵乘法（Matrix-Vector Multiplication，简称 GEMV，或 Matrix-Matrix，GEMM）”。关键在于如何加速量化后的权重与保持为 FP16（或 FP32）的激活值（输入数据）之间的乘加运算。&lt;/p>
&lt;h3 id="51-cpu环境下的-simd-指令利用">5.1. CPU环境下的 SIMD 指令利用
&lt;/h3>&lt;p>llama.cpp 在 CPU 推理中引以为傲的惊人速度，归功于汇编级别的 &lt;strong>SIMD (Single Instruction, Multiple Data)&lt;/strong> 优化。
例如在 Intel/AMD 的 CPU 上充分利用 &lt;strong>AVX2&lt;/strong> 或 &lt;strong>AVX-512&lt;/strong>，在 Apple Silicon 上充分利用 &lt;strong>ARM NEON&lt;/strong> 指令集。&lt;/p>
&lt;p>在推理过程中，并不是特意将 $W_q$ 还原为 FP32（反量化）之后再进行乘法运算。
而是对激活值一侧也按块进行动态量化（Dynamic Quantization，通常量化为 INT8），并利用 SIMD 特殊的点积指令（例如 &lt;code>vdpaddd&lt;/code> 或 &lt;code>_mm256_madd_epi16&lt;/code>），一次性计算 &lt;strong>INT8 $\times$ INT4&lt;/strong> 的整数运算。通过在最终的累加器中恢复为 FP32 并乘以缩放因子，实现了惊人的吞吐量。&lt;/p>
&lt;h3 id="52-gpu-环境-cublas--cuda-的卸载">5.2. GPU 环境 (cuBLAS / CUDA) 的卸载
&lt;/h3>&lt;p>最近的 llama.cpp 不仅支持 CPU，还对 NVIDIA GPU 有着强大的支持（CUBLAS / CUDA）。
可以将 GGUF 文件的部分或全部层卸载（Offload）到 VRAM 中（使用 &lt;code>--n-gpu-layers&lt;/code> 选项）。&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant User as 用户
participant CPU_RAM as CPU &amp; RAM (mmap)
participant VRAM as GPU VRAM
participant Compute as Tensor Cores
User->>CPU_RAM: 加载 GGUF (mmap)
CPU_RAM->>VRAM: 卸载层 (例如 30/32 层)
Note over CPU_RAM, VRAM: 数据在 VRAM 中保持量化状态
User->>Compute: 前向传播 (输入 Token)
Compute->>VRAM: 获取量化后的权重
Compute->>Compute: 在 SRAM 中实时反量化为 FP16
Compute->>Compute: 矩阵乘法 (cuBLAS / 自定义内核)
Compute->>User: 输出 Logits&lt;/div>
&lt;p>在 GPU 上计算时，VRAM 的带宽（Memory Bandwidth）是最大的瓶颈。由于权重被 k-quants 压缩，从 VRAM 到 GPU 运算单元（SM: Streaming Multiprocessor 或 Tensor Cores）的数据传输量减少到了 1/3 ～ 1/4。在权重到达计算单元的瞬间，会实时被反量化（展开）为 FP16，并使用 Tensor Core 进行超高速的矩阵乘法。
也就是说，进行量化&lt;strong>不是为了“减少计算量”，而是为了“减少内存传输量”&lt;/strong>。&lt;/p>
&lt;hr>
&lt;h2 id="6-内存使用量与性能权衡的具体例子">6. 内存使用量与性能权衡的具体例子
&lt;/h2>&lt;p>在这里，以 Llama 3 8B 模型为例，让我们看看不同 GGUF 量化级别的要求配置。（数值为大致参考）&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">模型/量化&lt;/th>
&lt;th style="text-align:left">文件大小&lt;/th>
&lt;th style="text-align:left">所需VRAM/RAM&lt;/th>
&lt;th style="text-align:left">推理速度(参考)&lt;/th>
&lt;th style="text-align:left">Perplexity 劣化&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (FP16)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">约 16 GB&lt;/td>
&lt;td style="text-align:left">18 GB以上&lt;/td>
&lt;td style="text-align:left">基准&lt;/td>
&lt;td style="text-align:left">无 (Base)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q8_0)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">约 8.5 GB&lt;/td>
&lt;td style="text-align:left">10 GB以上&lt;/td>
&lt;td style="text-align:left">高速&lt;/td>
&lt;td style="text-align:left">几乎为零&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q6_K)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">约 6.6 GB&lt;/td>
&lt;td style="text-align:left">8 GB以上&lt;/td>
&lt;td style="text-align:left">非常高速&lt;/td>
&lt;td style="text-align:left">极小&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q4_K_M)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">约 4.9 GB&lt;/td>
&lt;td style="text-align:left">6.5 GB以上&lt;/td>
&lt;td style="text-align:left">最快・最佳&lt;/td>
&lt;td style="text-align:left">可接受・微小&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q3_K_M)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">约 3.9 GB&lt;/td>
&lt;td style="text-align:left">5.5 GB以上&lt;/td>
&lt;td style="text-align:left">最快&lt;/td>
&lt;td style="text-align:left">稍显明显&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q2_K)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">约 3.0 GB&lt;/td>
&lt;td style="text-align:left">4.5 GB以上&lt;/td>
&lt;td style="text-align:left">高速&lt;/td>
&lt;td style="text-align:left">明显劣化&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>注意点 (KV缓存的影响):&lt;/strong>
在 LLM 推理中，如果上下文长度（提示词的 Token 数）变长，不仅是模型的权重，保存过去 Attention 状态的 &lt;strong>KV缓存&lt;/strong> 的内存消耗也会爆炸式增加。
例如上下文为 8192 个 Token 时，仅 KV缓存 就会消耗数 GB。因此，在实际应用中，需要保留 &lt;code>模型文件大小 + 约1.5GB～3GB&lt;/code> 的余量（Headroom）。推荐使用 Q4_K_M 的原因在于，即使保留了这个 KV缓存，它也是能在配备普通 8GB VRAM 的 GPU（如 RTX 3060 / 4060 等）上安全运行的绝佳折中方案。&lt;/p>
&lt;p>最近的 llama.cpp 还加入了 &lt;strong>将 KV缓存本身用 Q8_0 或 Q4_0 进行量化&lt;/strong> 的功能，为了进一步延长上下文长度而不断进行着各种创新。&lt;/p>
&lt;hr>
&lt;h2 id="7-总结">7. 总结
&lt;/h2>&lt;p>本文深入挖掘并讲解了作为 llama.cpp 心脏的 GGUF 格式以及 k-quants 量化技术的内部结构。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>GGUF的灵活性：&lt;/strong> 凭借键值对型的元数据结构，构建了一个强大的生态系统，即使面对 LLM 的快速进化（新模型架构的出现），也能在不产生破坏性更改的情况下跟上步伐。&lt;/li>
&lt;li>&lt;strong>k-quants带来的极限压缩：&lt;/strong> 通过超级块和子块的层次化缩放因子管理，在保留异常值信息的同时，实现了每个权重平均 4.8 位（Q4_K_M）的惊人压缩。&lt;/li>
&lt;li>&lt;strong>消除内存带宽瓶颈：&lt;/strong> 通过在 SIMD 和 CUDA 中的高级内核实现，在实时反量化的同时进行计算，从而减少了 VRAM 传输量，显著提升了推理速度。&lt;/li>
&lt;/ol>
&lt;p>推动 AI 民主化的 llama.cpp 的技术实力，可以说已经超越了单纯工具的范畴，是现代软件工程的最高峰之一。通过理解量化算法和 GGUF 格式的原理，你将能够更准确地为自己的环境选择最合适的模型并进行性能调优。&lt;/p>
&lt;h3 id="参考链接">参考链接
&lt;/h3>&lt;ul>
&lt;li>&lt;a class="link" href="https://github.com/ggerganov/llama.cpp" target="_blank" rel="noopener"
>llama.cpp GitHub Repository&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/ggerganov/ggml/blob/master/docs/gguf.md" target="_blank" rel="noopener"
>GGUF Format Specification&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/ggerganov/llama.cpp/pull/1684" target="_blank" rel="noopener"
>K-quants Implementation PR&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>（完）&lt;/p></description></item></channel></rss>