大模型如何理解你的话并生成回复
你在对话框里打了一句”今天天气怎么样”,按下回车。不到一秒,屏幕上开始一个字一个字地蹦出回复。
这中间发生了什么?模型是怎么”读懂”这句话的?又是怎么组织出一段通顺的回复的?
这篇文章从头到尾走一遍大模型的推理流程——模型已经训练好了,你问它一句话,它内部到底发生了什么。
分词:先把文字切碎
模型不认识”字”。它的最小处理单位是 token——一种介于字和词之间的片段。
拿”今天天气怎么样”举例,tokenizer 可能把它切成这样:
["今天", "天气", "怎么样"]
每个 token 对应词表里的一个整数 ID:
[8837, 25031, 48920]
这个切法不是按字典分词,而是用 BPE(Byte Pair Encoding)之类的算法,在大量文本上统计出来的高频子串。出现频率高的字符组合会被合并成一个 token,生僻的组合则被拆得更细。
中文通常一到两个汉字对应一个 token。英文里常见单词(like、the)往往整个词是一个 token,不常见的词会被拆成几段——比如 “tokenization” 可能变成 ["token", "ization"]。
词表大小通常在 10 万量级。这张表在训练前就固定了,推理时不会变。
嵌入:从文字到高维空间
有了 token ID,下一步是把每个 ID 变成一个向量。
模型内部有一张巨大的嵌入矩阵,大小是 词表大小 × 隐藏维度,比如 100000 × 4096。每个 token ID 去这张表里查对应的那一行,取出一个 4096 维的向量。
为什么要变成向量?因为后续所有计算都是矩阵运算,只能作用于数字。而且向量空间有一个好处:语义相近的词,在空间里的位置也靠得近。“猫”和”狗”的向量距离,比”猫”和”经济”的距离近得多。
但光有词义不够。“我打他”和”他打我”包含完全相同的三个 token,意思却完全不同。模型需要知道每个 token 在序列中的位置。
现代模型普遍采用 RoPE(旋转位置编码):不是给每个位置加一个固定向量,而是把位置信息编码成向量的旋转角度。这样模型天然能感知两个词之间隔了多远,而且对训练时没见过的序列长度也有一定的外推能力。
经过嵌入和位置编码后,输入变成了一个矩阵,形状是 [序列长度 × 隐藏维度],比如 3 个 token 就是 [3 × 4096]。
整个嵌入阶段的维度变化:
| 步骤 | 输入 | 变换 | 输出 |
|---|---|---|---|
| 查嵌入表 | token ID(标量) | 嵌入矩阵 100000 × 4096 | 向量 [4096] |
| 3 个 token | 3 个 ID | 各自查表 | 矩阵 [3 × 4096] |
| 加位置编码 | [3 × 4096] | RoPE 旋转变换 | [3 × 4096](形状不变,值变了) |
注意力机制:让每个词看见所有词
这个矩阵接下来要穿过几十甚至上百层 Transformer 层。每一层做两件事:自注意力和前馈网络。
自注意力:谁跟谁相关
自注意力是 Transformer 的核心。对序列中的每个 token,模型做这几步:
- 把它的向量通过三个不同的线性变换,生成三个新向量:Q(查询)、K(键)、V(值)
- 用当前 token 的 Q 去和所有 token 的 K 做点积,算出一组分数——“我应该关注谁”
- 对分数做 softmax,归一化成权重(加起来等于 1)
- 用这些权重对所有 token 的 V 做加权求和,得到一个融合了上下文信息的新向量
以 3 个 token 的序列为例,维度变化如下:
| 步骤 | 输入形状 | 变换矩阵 | 输出形状 |
|---|---|---|---|
| 生成 Q | [3 × 4096] | W_Q [4096 × 4096] | [3 × 4096] |
| 生成 K | [3 × 4096] | W_K [4096 × 4096] | [3 × 4096] |
| 生成 V | [3 × 4096] | W_V [4096 × 4096] | [3 × 4096] |
| Q · K 转置 | [3 × 4096] · [4096 × 3] | — | [3 × 3](注意力分数) |
| softmax | [3 × 3] | — | [3 × 3](归一化权重) |
| 权重 · V | [3 × 3] · [3 × 4096] | — | [3 × 4096] |
关键在第四行:Q 乘以 K 的转置后,输出是一个 [3 × 3] 的矩阵——每个 token 对其他所有 token 的注意力分数。这就是”每个词看见所有词”的数学含义。
举个具体的例子:处理”怎么样”这个 token 时,模型算出它对”天气”的注意力权重是 0.6,对”今天”是 0.3,对自身是 0.1。加权求和后,“怎么样”的新向量里就融入了”天气”和”今天”的语义。到这一步,模型已经把”怎么样”和”天气”关联起来了——它开始”理解”你问的是今天的天气怎么样。
多头注意力:从不同角度看关联
上面的过程不只做一次,而是并行做很多组(比如 32 组),每组叫一个”头”,使用不同的变换矩阵。
不同的头会学到关注不同维度的关系:有的头可能聚焦语法结构(主语和动词的对应),有的关注语义相似度,有的捕捉位置邻近性。最后把所有头的输出拼接起来,通过一个线性变换合并成一个向量。
前馈网络:加工和提取知识
注意力算完之后,每个位置的向量独立经过一个两层的全连接网络。通常第一层把维度膨胀到 4 倍(比如 4096 → 16384),经过激活函数后,第二层再压回原始维度。
| 步骤 | 输入形状 | 变换矩阵 | 输出形状 |
|---|---|---|---|
| 第一层(膨胀) | [4096] | W_1 [4096 × 16384] | [16384] |
| 激活函数 | [16384] | — | [16384] |
| 第二层(压缩) | [16384] | W_2 [16384 × 4096] | [4096] |
如果说注意力层决定了”关注哪些信息”,前馈网络就是对这些信息做深层加工。模型在训练中学到的大量事实性知识——谁是哪国总统、某个函数怎么调用——主要存储在前馈网络的参数矩阵里。
每一层末尾还有残差连接(把这一层的输入直接加到输出上)和层归一化,防止信号在几十层传递中消失或爆炸。
层层叠加
以上就是一个 Transformer 层做的全部事情。大模型通常堆几十到上百个这样的层。
每过一层,token 的向量表示就更”深”一些:浅层捕捉词法和简单句法,中间层处理语义关系,深层出现更抽象的推理模式。穿过所有层之后,每个位置上的向量已经不只代表那个词本身,而是编码了整个输入序列的上下文信息。
输出:从向量到概率分布
穿过所有 Transformer 层后,拿最后一个 token 位置的向量——它已经”看过”了前面所有内容。
这个向量乘以一个 [隐藏维度 × 词表大小] 的矩阵(叫输出头),得到一个长度等于词表大小的向量。里面每个数字对应词表中一个 token 的”得分”,术语叫 logits。
| 步骤 | 输入形状 | 变换矩阵 | 输出形状 |
|---|---|---|---|
| 取最后位置的向量 | [3 × 4096] | — | [4096] |
| 乘输出头 | [4096] | W_out [4096 × 100000] | [100000](logits) |
| softmax | [100000] | — | [100000](概率分布) |
对 logits 做 softmax,就变成了概率分布——10 万个 token 各自有一个概率:
"晴" → 0.15
"不" → 0.12
"我" → 0.08
"很" → 0.07
"今" → 0.05
...(剩余 10 万个 token 都有一个概率)
到这一步,模型的工作本质上就是:给定前面的所有内容,告诉你下一个 token 各有多大可能是什么。
采样:从 10 万个候选里选一个
有了概率分布,怎么选下一个 token?直接选概率最高的看起来最合理,但实际效果往往不好——生成的文本会很机械,容易陷入重复。所以需要采样策略。
贪心(Greedy) 最简单:每次选概率最高的 token。确定性最强,但最死板。
Temperature 在 softmax 之前用一个温度系数缩放 logits。温度低(比如 0.3),概率分布更尖锐,高概率 token 更容易被选中,输出更确定。温度高(比如 1.2),分布更平坦,低概率 token 也有机会出头,输出更有”创意”,但也更容易跑偏。
Top-p(核采样) 把 token 按概率从高到低排列,累加直到总概率达到 p(比如 0.9),只在这个子集里采样。既保证了多样性,又排除了概率极低的离谱选项。
Top-k 更粗暴:只从概率最高的 k 个 token 里选。
实际使用中通常组合多种策略,比如 Top-p = 0.9 加 Temperature = 0.7。不同场景调不同参数——写代码时温度低一点求准确,写故事时温度高一点求发散。
自回归:一个字一个字地蹦
选出了一个 token(比如”今”),把它拼到输入序列的末尾,然后整个序列再走一遍上面的流程——嵌入、穿过所有 Transformer 层、输出头、采样——再选出下一个 token。如此循环往复。
这就是自回归生成:每一步都依赖前面所有已生成的内容,一个 token 一个 token 地往外蹦。
循环终止的条件有两个:生成了特殊的结束符(EOS),或者达到了预设的最大长度。模型生成”今天天气不错”这 5 个 token,实际跑了 5 次完整的前向传播。
这里有一个重要的工程优化叫 KV Cache。注意前面讲注意力时,每个 token 要算 K 和 V 向量。生成新 token 时,前面所有 token 的 K 和 V 跟上一轮算出来的一模一样,没必要重算。模型会把它们缓存起来,每轮只算新 token 的部分,再跟缓存拼在一起做注意力。这也是为什么大模型推理时显存占用会随生成长度不断增长——缓存越来越大。
有一个容易产生的误解需要澄清:模型没有”先想好整句话再说”这个过程。 它在每个时间步只看到前面已有的内容,预测下一个 token。看起来连贯通顺的回复,是上下文信息在每一步的概率分布中自然引导出了语义连贯的选择——不是规划的结果,而是统计规律的涌现。
7B、70B:参数量到底是什么
聊完推理流程,一个自然的问题是:常看到”7B 模型""70B 模型”,这些数字指什么?
参数就是模型里所有可学习的权重数字。 上面每一步涉及的矩阵,里面的每个数字都是一个参数。粗略算一下:
- 嵌入矩阵
100000 × 4096:约 4 亿个参数 - 每层注意力的 Q/K/V/输出投影矩阵:约 6700 万参数
- 每层前馈网络的两个矩阵:约 1.3 亿参数
- 单层合计约 2 亿参数
堆 96 层就接近 200 亿。加上嵌入和输出头,就到了某个 B 级别。B 是 billion(十亿),T 是 trillion(万亿):7B = 70 亿参数,70B = 700 亿参数。
决定参数量的本质上是三个旋钮:隐藏维度(向量有多宽)、层数(堆多少层)、注意力头数和前馈网络膨胀倍率(每层有多复杂)。三者一起放大,参数量就从 7B 涨到 70B。
参数量决定了模型的容量上限——能记住多少知识、能处理多复杂的推理。但大不等于好。同样 7B 的模型,用不同质量的训练数据、不同的对齐策略,效果可以差出一个量级。近年的趋势是让小模型吃更多高质量数据,比如 Llama 3 8B 的训练数据量远超同体量前辈,效果已经追上了早期 70B 模型。
参数量也直接决定推理成本:你每问一句话,就是让输入依次乘过所有这些参数矩阵。70B 比 7B 大 10 倍,计算量和显存占用也大约是 10 倍。
这套机制的代价和局限
理解了推理流程,几个常见现象就有了解释。
为什么第一个字出来慢,后面就快了? 第一个 token 的生成需要处理整段输入(可能有几千个 token),所有位置之间两两算注意力,计算量很大。这一步叫 prefill。之后每生成一个 token,有 KV Cache 兜底,只需算新 token 和历史 token 之间的注意力,增量计算量小得多。
为什么上下文长度有限制? 注意力的计算量随序列长度平方增长——1000 个 token 时算 100 万次点积,10000 个 token 就是 1 亿次。KV Cache 的显存占用也随长度线性增长。所以每个模型都有上下文窗口上限,超了要么截断要么报错。
为什么会”一本正经地胡说八道”? 模型在每一步只是做概率预测:给定前文,下一个 token 最可能是什么。它没有”我不确定”这个内部信号。如果训练数据里某个说法模式出现频率很高,模型就会很”自信”地沿着这个模式生成下去,哪怕内容是错的。这是幻觉(hallucination)的根源之一。