生成式 AI 实在太火了,几乎无处不在,隔三差五就刷爆朋友圈。你多半已经用过 ChatGPT 了,甚至可能已经把它当成了工作中的智能小助手。

但说实话,很多人心里都藏着一个疑问:这些AI模型的“智能”到底是怎么来的?今天,咱们就来把这个话题彻底聊透。
我会尽量避开那些让人头大的高等数学术语,用大家都能听懂的大白话,把生成式文本模型的工作原理一层层剥开,让它回归到最简单的计算机算法本质。
01|LLM 到底在干什么?
首先,得澄清一个关于LLM工作原理的重大误解。
很多人想当然地以为,这些模型能回答问题、能跟你聊天,是一种有“思想”的存在。但本质上,它们只做了一件事:接收你提供的一段文本作为输入,然后猜下一个词(更准确地说,是下一个Token)是什么。
要理解LLM的奥秘,得先从Token说起。
Token:模型理解的基本单位
Token就是LLM能理解的最小文本单位。虽然直接把它当成“单词”来理解很方便,但对LLM来说,它的目标是尽可能高效地编码文本。
所以,在实际操作中,一个Token可以比一个单词短,也可以比一个单词长。标点符号和空格也都被编码成Token,单独存在或者跟其他字符组合在一起。
所有Token加在一起,就构成了模型的“词汇表”——只要是能想到的文字,理论上都可以用它来表示。LLM生成Token词汇时,通常会用一种叫字节对编码(BPE)的算法。
给你一个直观的数字感受:开源模型GPT-2的词汇表大小为50,257个Token。每个Token都有一个独一无二的数字ID。模型通过一个叫“分词器”的工具,在普通文本和Token数字列表之间来回转换。
如果你熟悉Python,可以装个OpenAI的tiktoken包亲自体验一下:
$ pip install tiktoken
然后在Python提示符里尝试:
>>> import tiktoken
>>> encoding = tiktoken.encoding_for_model("gpt-2")
>>> encoding.encode("The quick brown fox jumps over the lazy dog.")
[464, 2068, 7586, 21831, 18045, 625, 262, 16931, 3290, 13]
>>> encoding.decode([464, 2068, 7586, 21831, 18045, 625, 262, 16931, 3290, 13])
'The quick brown fox jumps over the lazy dog.'
>>> encoding.decode([464])
'The'
>>> encoding.decode([2068])
' quick'
>>> encoding.decode([13])
'.'
从实验里不难发现,GPT-2的Token 464代表单词"The",而Token 2068代表" quick"(注意前面有个空格)。句号则由Token 13表示。
由于Token完全是算法算出来的,你可能会碰到一些有意思的事儿。比如单词"the"的三种不同写法,在GPT-2里被编码成了不同的Token:
>>> encoding.encode('The')
[464]
>>> encoding.encode('the')
[1169]
>>> encoding.encode(' the')
[262]
BPE算法也不是非得把整个单词映射成一个Token。那些不常用的单词,就成不了独立的Token,得拆成多个Token来编码:
>>> encoding.encode("Payment")
[19197, 434]
>>> encoding.decode([19197])
'Pay'
>>> encoding.decode([434])
'ment'
下一个Token预测:最核心的任务
正如刚才说的,给定一段文本,语言模型的任务就是预测紧跟其后的下一个Token是什么。
用Python伪代码来描述,可能会更清楚。下面就是模型预测下一个Token的常规流程:
predictions = get_token_predictions(['The', ' quick', ' brown', ' fox'])
这个函数接收的是用户输入的提示词(pormpt)编码而来的Token列表。为了更直观,这里用了Token的文本形式,但实际传给模型的其实是数字。
函数返回的是一个数据结构,里面为词汇表中的每一个Token都分配了一个概率值,表示它紧跟当前文本出现的可能性有多大。
如果是GPT-2,这个返回值就是一个包含50,257个浮点数的列表,每个数字对应一个Token的概率。
大致想象一下:一个训练良好的语言模型,会给"jumps"这个Token一个比较高的概率,让它去接"The quick brown fox"这串提示。而像"potato"这种完全不着调的词,概率自然就无限接近于0。
为了让预测结果更合理,语言模型必须经历一个训练过程。训练时,模型被喂进海量的文本,然后在训练结束后,它就可以利用从所有文本中“学”到的数据结构,来计算给定Token序列的下一个Token概率了。
怎么样,是不是跟你想的不太一样?希望这么一拆解,它看起来没那么玄乎了。
生成长文本:一次只猜一个词
既然模型一次只能猜一个Token,那要生成完整的句子,就只能循环运行模型,让预测结果不断加入输入,再继续预测下一个。
看看下面这段更完整的Python伪代码:
def generate_text(prompt, num_tokens, hyperparameters):
tokens = tokenize(prompt)
for i in range(num_tokens):
predictions = get_token_predictions(tokens)
next_token = select_next_token(predictions, hyperparameters)
tokens.append(next_token)
return ''.join(tokens)
generate_text()函数接收用户输入的提示。tokenize()辅助函数用tiktoken这类库把提示转成Token列表。
循环里,get_token_predictions()调用AI模型获取下一个Token的概率。select_next_token()则根据这些概率决定选哪个Token来继续生成。
最简单的做法,是直接选概率最高的Token——这在机器学习里叫“贪心选择”。更好的办法是引入随机性:用随机数生成器,按模型给的概率去抽样选取Token。这样既能给生成结果增加一些变化,还能让模型对同一个提示多次做出不同的回应。
为了使Token的选择过程更灵活,LLM返回的概率还可以用一些超参数来调整。如果你用过LLM,应该对temperature不陌生。温度越高,Token的概率分布越“平坦”,意味着更有可能选到那些不太常见的Token,生成结果自然就显得更有“创造性”或更出人意料。另外两个常用的超参数是top_p和top_k,它们用来控制考虑范围内,概率最高的Token数量。
每选好一个Token,循环就迭代一次。模型接收加入新Token后的完整输入,再生成下一个Token。num_tokens参数控制循环次数,也就是要生成多少文本。
由于LLM根本没有“句子”或“段落”的概念,它只处理Token,所以生成的文本经常会在句子中间突然断掉。为了规避这个现象,可以把num_tokens当成一个上限值,而不是精确的Token数,比如在生成句号Token时就主动停止循环。
如果你已经理解了这个过程,那恭喜你,你现在已经大致搞懂了LLM是怎么工作的了。下一部分,咱们会再深入一点,但尽量还是避开那些复杂的数学原理。
02|模型是怎么训练出来的?
不用数学表达式来讨论模型训练,确实挺难的。咱们从一个特别简单的方法开始。
既然任务是预测Token后面该跟什么Token,那最朴素的做法就是:把训练数据集中所有相邻的Token对,整理成一个概率表。
假设模型的词汇表只有以下5个Token:
['I', 'you', 'like', 'apples', 'bananas']
训练数据集由三句话组成:
- I like apples
- I like bananas
- you like bananas
我们可以构建一个5×5的表格,每个单元格记录行代表的Token后面,跟着列代表的Token的次数。
从数据集中统计出来的结果如下:
(此处为概率表格的文本描述,原文未提供具体数字表格,但后续推理表明:I后跟like出现2次;you后跟like出现1次;like后跟apples出现1次,跟bananas出现2次;apples和bananas没有后续。)
有了次数之后,就能算概率了。比如“like”后面,有33.3%的概率是“apples”,66.7%的概率是“bananas”。
但问题来了:“apples”和“bananas”这两行根本没有数据,因为它们后面没出现过任何Token。这就是训练数据里的“漏洞”。为了不让模型卡住,可以手动给这些空缺的行平均分配一个概率到其他4个Token上——虽然可能产生奇怪的结果,但至少模型不会就此罢工。
训练数据中的漏洞问题,在真实LLM里其实挺重要。真正的LLM训练集巨大无比,明显像上面这种大窟窿不会出现。但因为数据覆盖不全导致的小漏洞,却是普遍存在的。在这些“训练不足”的区域,LLM对Token的预测质量会打折扣,只是通常不容易察觉。这也是LLM有时会“胡编乱造”(产生幻觉)的原因之一——生成的文本读起来很顺溜,但里面全是事实错误或前后矛盾。
有了上面的概率表,get_token_predictions()函数的实现就简单了:
def get_token_predictions(input_tokens):
last_token = input_tokens[-1]
return probabilities_table[last_token]
是不是比想象中简单?这个函数拿提示词序列的最后一个Token,去概率表里找对应行。比如输入 ['you', 'like'],就返回 'like' 那一行,33.3%的概率选apples,66.7%的概率选bananas。当 apples 被选中时,“you like apples”这个在训练集中不存在、但又完全合理的句子就诞生了。看到没,模型就是这样通过“重新组合”自己在训练中学到的不同片段,来生成看似原创的内容。
上下文窗口:看得远才能写得连贯
上一节的训练方法,本质上是一种“马尔可夫链”。它有个很大的问题:只用上一次输入的最后一个Token来预测下一个,完全不管前面的内容。这种方案的“上下文窗口”只有1个Token,小得可怜。
这么小的窗口,模型很快就会“忘记”刚才的思路,写出来的东西东一榔头西一棒槌,缺乏连贯性。解决方法是把上下文窗口拉大。比如用两个Token作为预测依据,那就得在概率表里增加代表所有两两Token组合的行。
当词汇表是5个Token时,两两组合会新增25行,再加上原来的5行单Token行。模型需要重新训练,这次不仅要看Token对,还要看Token组的三元组。在每次调用get_token_predictions()时,当可用输入至少有两个Token时,就取最后两个Token去查表。
但问题依然存在:2个Token的上下文窗口还是不够大。要生成连贯、有点意义的文本,上下文窗口必须更大。扩大到3个Token,概率表增加125行,效果还是不行。
那到底要多大?OpenAI开源的GPT-2,上下文窗口是1024个Token。如果用马尔可夫链来实现,每行概率表都得代表一个长度在1到1024之间的Token序列。就5个Token的词汇表来说,长度为1024的可能序列就有5的1024次方种。这个数字大到无法想象。
更别说,我们还需要长度为1023、1022...一直到1的序列,来应对输入不足的情况。马尔可夫链的可扩展性差得太离谱了。
而且,1024个Token在现在看来也不算大了。GPT-3升到了2048,GPT-3.5到了4096,GPT-4先从8192起步,一路涨到32K、128K(没错,12.8万Token)。现在甚至有上下文窗口达到1M甚至更大的模型了。窗口越大,做Token预测时的连贯性和“记忆力”就越强。
所以,马尔可夫链虽然能帮我们从正确的角度思考文本生成,但它本身并不是一个可行的方案。
从马尔可夫链到神经网络
既然概率表的方案行不通(因为合理上下文窗口的表需要天文数字般的RAM),那能怎么办呢?可以用一个函数来替代概率表,这个函数不是去存储庞大的表格,而是用算法来“近似”算出Token的概率。而这正是神经网络最擅长的事。
神经网络是一种特殊的函数:它接收一些输入,经过一些计算,然后返回一个输出。对语言模型来说,输入是表示提示词的Token,输出是下一个Token的概率分布。说它“特殊”,是因为它的行为除了逻辑本身,还受一组外部定义的参数控制。
一开始,这些参数是未知的,所以函数输出基本没用。训练过程就是不断寻找能让函数在训练数据集中表现最佳的参数。训练时,参数会通过一种叫“反向传播”的算法(涉及大量数学,这里不展开)被反复微调,每次微调后,模型的预测结果就会好一点点。这个过程会一直持续到模型能很好地预测下一个Token为止。
给你一个规模概念:GPT-2有大约15亿个参数,GPT-3飙升到1750亿,而GPT-4据说已经达到了1.76万亿。以现有硬件条件训练这种规模的网络,通常要花数周甚至数月时间。
有意思的是,因为参数太多,又都是在无人干预的漫长迭代中自动算出来的,所以人类很难理解模型到底是怎么工作的。一个训练有素的LLM就像一个黑匣子,极其难以调试。哪怕是亲自训练它的人,也很难说清其内部运作的每个细节。
层、Transformer 和 注意力机制
你可能会好奇,这个神奇的神经网络函数内部,到底发生了什么?在参数的帮助下,把一串输入Token变成合理的下一个Token预测,这个过程中间藏着哪些操作?
神经网络被配置成执行一连串的操作,每个操作叫做一个“层”。第一层负责接收输入,做一次变换;变换后的结果进入下一层,再被变换一次;如此反复,直到数据到达最后一层,完成最后一次变换,生成最终的输出。
机器学习专家们设计了各式各样的层,对数据进行不同的数学变换,并摸索出如何组合这些层来达到预期结果。有些层是通用的,有些则专门为处理特定类型的数据而设计,比如图像,或者在LLM中间出现的文本Token。
目前,用于文本生成的最流行的神经网络架构叫“Transformer”。使用这种设计的LLM,就叫GPT(生成式预训练Transformer)。Transformer的一个核心特点是,它内部有一种叫做“注意力机制”的层计算。这种机制让模型能在上下文窗口内的所有Token之间,推导出各种关系和模式,并把这些关系和模式反映到下一个Token的概率预测中。
注意力机制最初是用在语言翻译器上的,作为一种找出输入序列中最重要的词、从而提取其含义的方法。它使得现代翻译器能在基本层面上“理解”一个句子——通过关注(或者说把“注意力”引向)那些关键的词或Token。
03|LLM 真的具备智能吗?
看到这里,你可能对LLM是否在生成文本的过程中表现出了某种智能,已经有了自己的判断。
严格来说,并不认为LLM具备真正的推理能力或提出原创思想的能力。但这绝不意味着它们毫无用处。
得益于对上下文窗口内Token的巧妙计算,LLM能快速发现用户提示中存在的模式,并将这些模式与训练期间学到的类似模式对应起来。它们生成的文本,本质上是训练数据的“拼接重组”。但拼接的方式实在太复杂了,在很多情况下,结果确实让人感觉既原创又实用。
鉴于LLM容易产生幻觉,任何未经人工验证就直接交给终端用户的生成结果,都是不能信任的。
未来几个月或几年内,更大规模的LLM会实现真正的智能吗?至少在当前GPT架构下,可能性不大,因为它自身的限制太多了。但谁知道呢?也许未来的某次创新,就会让这个目标成真。
