啃透 LLM 预测下一个词的核心逻辑后,我终于不觉得大模型神秘了

0 阅读13分钟

我之前一直有个解不开的疑惑 —— 我们敲一段文字发给大模型,它就能顺理成章接出一长串回答,既能写代码又能改文案,甚至还能跟你掰扯道理。它到底是怎么知道 “下一个词” 该接什么的?难道它真的能读懂我们说的话?

说实话这个问题存了好久,之前看 Transformer 的文章总觉得术语堆得太密,看一半就绕晕了。这周逼着自己从最底层的输入开始,一步一步往输出捋,连画带写折腾了三天,总算把整条链路掰明白了。说出来你可能觉得离谱,不管是 GPT、Qwen 还是现在市面上绝大多数大模型,从训练到推理,本质上就只会干一件事:根据你给的上文,算出下一个位置每个词的概率,然后挑概率最高的那个输出。

就这么简单?就这么简单。但简单的规则堆叠到足够大的规模,就有了我们现在看到的各种神奇能力。

先破个误区:大模型根本不认 “字”,只认词元

我们输入的是自然语言,是汉字、英文单词,但模型根本不认这些。它拿到输入的第一件事,就是把我们的话切成一个个 “词元”,也就是 token,再把每个 token 转成对应的数字编号 ——tokenId,这才是模型真正能处理的输入。

最开始我以为这不就是分词吗?中文分词我懂啊,把一句话切成词语。后来才发现根本不是一回事。比如英文里的 unhappiness,我们眼里是一个完整的单词,但在大模型眼里,它大概率会被拆成 unhappiness 三个词元;再比如中文里的 “人工智能”,有时候是一个词元,有时候也可能被拆开,全看模型的词表是怎么设计的。甚至连逗号、句号、换行空格,都可能是单独的词元。

为什么好好的词要拆碎了用?

我第一反应是这不是多此一举吗?直接按单词处理不行吗?后来算了笔账就懂了。要是英文所有单词都单独做成词元,词表轻轻松松就得几十万个;中文更夸张,常用词加起来几百万都打不住。词表越大,模型最后算概率的计算量就越大,实际跑起来根本扛不住。

把词拆成更小的 “积木块” 就不一样了。几万个子词单元,就能拼出几乎所有的常用词汇。比如你认识了 unhappiness,不光能拼出 unhappiness,还能拼出 unlucky、happiness 这些词,性价比高太多了。

补充个冷知识你平时调用大模型 API 按 token 计费,算的就是这个词元的数量,不是汉字数。中文大概 1.3~1.5 个字对应一个 token,以后算费用别直接拿汉字数硬乘,容易算错。

你也可以把 token 理解成大模型世界里的 “通用货币”—— 输入要按 token 算,输出要按 token 算,计费也按 token 算。所有的文字进了模型,都得先兑换成这种货币,才能继续往下走。

QQ_1783328360401.png

从编号到语义:Embedding 到底在干嘛

tokenId 说白了就是个编号,跟你去超市取的储物柜号码牌一样,只是个索引,本身没有任何语义。比如 “你” 对应的 tokenId 是 57788,这个数字本身啥也不是,你总不能拿 57788 去加减乘除,算出 “好” 对应的编号吧?

我最开始卡在这里卡了好久 —— 数字怎么就能算出语义了?后来才搞明白,模型根本不直接算编号。它有一张巨大的 “向量查找表”,学名叫 Embedding Matrix,每个 tokenId 都对应一个高维的向量。这个向量,就是这个词元的 “语义坐标”。

语义真的能算出来吗?

说出来挺神奇的,模型训练完之后,语义相近的词,在这个高维空间里的距离就会很近。比如 “猫” 和 “狗” 的向量离得近,“猫” 和 “桌子” 的向量就离得远。

更有意思的是向量还能做运算。最经典的那个例子:国王的向量,减去男性的向量,再加上女性的向量,结果会非常接近王后的向量。我第一次看到这个例子的时候鸡皮疙瘩都起来了 —— 原来语义关系真的能转化成几何运算。

QQ_1783328804412.png

这个把 tokenId 转换成语义向量的过程,就叫 Embedding。你可以理解成模型有一个大柜子,每个格子上写着 tokenId,格子里放着这个词的语义说明书(向量)。拿到一个编号,就去对应的格子里把说明书抽出来,后面所有的计算,都是拿着这些说明书在算。

这些向量不是人为设计的,是模型在预训练阶段,看了海量的文本之后,自己慢慢学出来的。它自己琢磨出了每个词该放在坐标系的哪个位置,才能最好地预测下一个词。

光有语义还不够:顺序错了意思能反过来

捋到这里我又发现了一个问题:如果每个词只有语义向量,那 “我咬了狗” 和 “狗咬了我”,四个词完全一样,向量加起来也没区别,意思却天差地别。模型怎么分清楚谁是施动者谁是受害者?

对,这就是 embedding 本身解决不了的问题 —— 它不携带位置信息。词的顺序变了,语义完全不一样,所以我们必须给每个向量额外 “叠” 上一层位置信息,告诉模型:这个词出现在句子的第几个位置。

这就是位置编码(Position Encoding)。叠加之后,每个词元的向量里就同时装了两类信息:

  • 语义信息:这个词本身是什么意思
  • 位置信息:这个词在当前句子里排第几

说实话我第一次看到的时候觉得特别朴素,甚至有点 “就这?” 的感觉。但就是这么简单的一步,补上了顺序这个关键缺口,后面的注意力机制才能正常工作。

模型怎么读懂上下文?自注意力到底是个啥

解决了 “每个词是什么” 和 “每个词在哪里”,接下来就是最核心的问题了:模型怎么理解上下文?

举个经典的例子:

The animal didn't cross the street. Because it was too tired.

这个 it 指代的是什么?人一眼就能看出来是 animal,不是 street。模型是怎么判断的?

这就要说到 Transformer 的灵魂 —— 自注意力(Self-Attention)了。简单说,自注意力的作用,就是让句子里的每个词,都去跟其他所有词 “对一对眼神”,看看谁跟自己相关性最高,然后把对方的语义信息拿过来,丰富自己的含义。

就像上面的 it,它自己本身是个代词,没太多实际含义。但经过自注意力之后,它就会从 animal 那里拿到大量语义信息,于是模型就知道了:哦,这个 it 说的是那个动物。

QKV 到底是什么?我用个例子就想通了

自注意力里最绕人的就是 Q、K、V 三个东西,我记了忘忘了记好多次,后来用个生活化的类比一下就记住了。

你可以把每个词元都想象成一个参会的人:

  • Q(Query) :相当于我手里的寻人启事,写着我要找什么样的人
  • K(Key) :相当于我胸前挂的名片,写着我是谁、我有什么特征
  • V(Value) :相当于我手里带的资料,是我真正能提供出去的内容

自注意力计算的过程也很直白:

  1. 每个词都拿出自己的 Q,去跟句子里所有词的 K 挨个做计算(一般是点积),算出一个 “相关性分数”
  2. 分数越高,说明两个词的关联越强
  3. 最后按照分数的比例,把所有词的 V 加权求和,得到的就是这个词融合了上下文之后的结果

回到刚才的例子,it 拿出自己的 Q,分别去跟 animal、street、tired 这些词的 K 算分数。算下来跟 animal 的分数最高,跟 street 的分数很低,所以最后 it 的语义里,animal 的内容占比最大,模型自然就知道 it 指代的是动物了。

QQ_1783328841013.png

我当时自己对着例子手算了一遍分数,瞬间就通透了。原来所谓的 “理解上下文”,本质上就是这么一套加权求和的数学运算,没有什么玄学。

当然真实的 Transformer 里是多头注意力,就是同时做好几组这样的计算,每组关注不同的关联关系,有的关注指代,有的关注动作搭配,有的关注语法结构。但核心逻辑,还是这套 QKV 的匹配。

我自己写了个极简的模拟脚本,把注意力分数的计算简化了一下,你跑一下就能直观感受到相关性是怎么算出来的:

// 极简模拟:两个词的QK注意力分数计算
// 注意:真实场景是几千维的向量,这里用2维简化演示,逻辑是一样的
function calcAttentionScore(query, keys) {
  // 点积运算,分数越高说明两个词相关性越强
  // 别问我为什么特意写这么直白,最开始我被“点积”俩字唬住好久,手算三遍才懂
  return keys.map(key => {
    const score = query[0] * key[0] + query[1] * key[1];
    return score;
  });
}

// 模拟几个词的向量(随便编的数值,仅演示逻辑)
const animal_K = [3, 4];   // animal 的 Key 向量
const street_K = [1, 1];   // street 的 Key 向量
const it_Q     = [2.8, 3.9]; // it 的 Query 向量

const scores = calcAttentionScore(it_Q, [animal_K, street_K]);

console.log("it 和 animal 的注意力分数:", scores[0]); 
console.log("it 和 street 的注意力分数:", scores[1]);

// 运行输出:
// it 和 animal 的注意力分数: 24
// it 和 street 的注意力分数: 6.7
// 一目了然,animal 和 it 的相关性高得多

串起来看:一整个预测流程是怎么跑的

现在把前面这些部件串起来,从你输入一句话,到模型输出第一个词,整个流程就特别清晰了:

第一步,分词转编号。你输入的 prompt 先被分词器切成一个个 token,再转成对应的 tokenId 数组,完成从自然语言到模型能识别的数字的转换。

第二步,转语义向量。每个 tokenId 去 Embedding 表里查,拿到对应的高维语义向量,把冰冷的编号变成有语义的坐标点。

第三步,加位置编码。给每个语义向量叠加上位置信息,让模型知道每个词的先后顺序,区分开 “我咬狗” 和 “狗咬我”。

第四步,自注意力计算。经过多层多头自注意力,每个词都和上下文所有词做一遍关联计算,把上下文的信息融合进自己的向量里。这一步之后,每个词就不再是孤立的了,而是带着整句话的语境。

第五步,算概率输出。最后模型根据最后一个位置的向量,算出词表里每个词作为 “下一个词” 的概率,选出概率最高的那个,转成文字输出给我们。

这还没完。输出第一个词之后,模型会把这个新词加到原来的输入末尾,再完整跑一遍上面的五步流程,算出第二个词…… 就这样一个接一个地往后生成,直到输出结束标记,或者达到你设置的最大长度。

这个 “生成一个、加回去、再生下一个” 的模式,就叫自回归生成。说白了就是踩着自己刚输出的脚印,一步一步往前走。

我踩过的几个认知坑

捋整个流程的时候我踩了好几个想当然的坑,这里也列出来,省得你再走弯路。

第一个坑,我以为模型一次生成一整句话。最开始我以为模型拿到输入,一下就算出了所有输出内容。后来才知道,它真的是一个词一个词往外蹦的。每生成一个新词,都要把前面所有的内容(包括已经生成的)重新过一遍模型,再算下一个。这也是为什么上下文越长,生成速度越慢 —— 每一步要处理的 token 都变多了。

第二个坑,我以为 token 就是汉字 / 单词。之前算 API 费用的时候我直接拿汉字数乘单价,后来发现总对不上。才知道中文里有的常用词是一个 token,有的生僻字可能一个字占好几个 token,标点、换行也都是单独计费的。真要精确算,还是得用官方的分词工具跑一遍。

第三个坑,我以为模型真的 “懂” 内容。说实话,捋完整个流程我最大的感受就是:大模型其实根本 “不懂” 它在说什么。它不知道 “北京是中国的首都” 这件事对不对,它只知道根据训练数据里的统计规律,“中国的首都是” 后面接 “北京” 的概率最高。所以它才会一本正经地胡说八道 —— 当概率最高的选项恰好是错的,它就会毫不犹豫地输出错误答案。它没有对错观念,只有概率观念。

最后说两句

其实写到这里,整个预测下一个词的核心链路就讲完了。真实的 Transformer 架构比这个复杂得多,有残差连接、层归一化、前馈神经网络,还有几十上百层的堆叠,工程上的优化更是数不胜数。但把这些外壳都剥掉,最核心的逻辑就是这么朴素。

这几天啃下来我有三个特别实在的收获:第一,很多看起来很玄乎的技术,拆到底层都是清晰的规则,别被高大上的名词吓住,顺着输入到输出的链路捋,总能捋明白。第二,别小看 “预测下一个词” 这件小事。当模型的参数量足够大、训练数据足够多,简单的规则重复足够多次,就会涌现出很多我们意想不到的能力。第三,理解了 token、注意力、自回归这些基础概念之后,再去写 prompt、调接口、排查生成问题的时候,心里就有底了,不会再觉得大模型是个摸不透的黑盒子。

当然了,这点认知也只是皮毛。比如注意力具体怎么训练出来的、多头到底是怎么分工的、为什么大模型会涌现能力,这些问题我也还在慢慢啃。

如果你也在琢磨这块内容,看完觉得有点启发,或者有不一样的理解,欢迎在评论区留个言,我也想看看大家是怎么理解这些东西的。