引言
近几年来,以 GPT-3、ChatGPT 为代表的大语言模型(LLM)展现出了惊人的“理解”和生成语言的能力,让很多人惊叹它们似乎会思考。但实际上,LLM 的内部运作更接近于一种复杂的概率计算过程,而非真正的人类思维 。简单来说,模型根据输入推断出下一步最可能出现的词,然后将其输出。要揭开 LLM “思考”的面纱,我们需要梳理它从接收文本输入到生成最终答案的整个流程。本篇文章将结合原理讲解与工程实践,参照 Hugging Face 博客与极市平台文章的风格,全面介绍大语言模型的工作机制,包括:文本分词为 Token(词元)、Token 向量化(Embedding),以及模型如何通过概率分布采样生成输出文本,并澄清一些常见的误解。
思维导图示意: 可以将 LLM 处理文本的过程想象为一条流水线:
- 输入文本 – 模型接收到原始的文本字符串。
- 分词 (Tokenization) – 将文本拆解成一个个 Token(词元) 序列。
- 映射 ID – 每个 Token 对应到模型词表中的一个 整数 ID 。
- 向量化 (Embedding) – 使用嵌入矩阵将每个 Token ID 转换为高维向量表示 。这一步使得计算机能够用数字“理解”文本语义。
- 模型推理 – 模型(例如 Transformer 神经网络)接收这些 Token 向量,经过多层计算,产生对下一 Token 的预测评分(logits)。
- Softmax 概率分布 – 将 logits 通过 Softmax 函数转换为每个词的概率分布 。
- 采样生成 – 根据设定的解码策略(如贪心、Top-k、Top-p、温度采样等)从概率分布中选出下一个输出 Token。然后将该 Token 添加到输出序列,并重复步骤5-7,直到生成完整答案。
下面我们将沿着这条“思维”流程,逐步揭秘大语言模型如何从分词到推理再到最终的文本生成。
文本分词:从字符串到 Token 序列
为什么要分词? 大语言模型无法直接处理原始文本字符——模型只能理解数字。因此,输入的句子首先必须转换为数字才能送入模型 。这个转换过程的第一步就是分词(Tokenization)。简单来说,分词就是将一串文本切分为一个个较小的单元,这些单元被称为 Token(词元)。每个 Token 通常对应一个词、一段子词,甚至可能是一个字符或符号,具体取决于所使用的分词算法和词汇表。分词的结果,是把文本转化为一个 Token 序列。
如何分词? 为了高效地表示不同语言内容,现代 LLM 常使用子词分词算法,例如 Byte-Pair Encoding (BPE,字节对编码) 或 WordPiece 等。以 BPE 为例,它通过反复合并高频的字符序列来构建词汇表,使模型的词表既不至于过大,又能将常见词或片段作为整体 Token 表示。例如,在英文中,“international” 这样的词可能被拆分为 “inter”、“na”、 “tion”等子词;在中文中,常见的词组(如“机器”“学习”)也可能作为一个 Token。BPE 分词器会根据训练语料统计常见的字符对,将它们合并为新的 token,不断迭代。这意味着模型的词汇表中既包含完整单词,也包含这些高频子词或字片段。
Token 与 ID: 无论采用哪种分词算法,最后都会得到一系列 Token。每个 Token 再通过查表被映射为一个唯一的整数 ID,方便模型处理 。可以把模型的词汇表想象成一个巨大“字典”,里面收录了模型所能识别的所有 Token 及其对应编号。例如,GPT-2 模型的词汇表大小约为 50,257 ;每个 Token(无论是“apple”这样的完整单词,还是“ap”这样的子词,亦或是标点符号等)都对应于 0 到 50256 之间的某个ID。
**一个直观示例:**假如我们有一句英文输入 “I have a dream”,使用 GPT-2 的 BPE 分词器会将其拆成 Token:[“I”, ” have”, ” a”, ” dream”](其中空格也作为 Token 一部分)。再比如中文句子“我有一个梦想”,若使用中文分词器,可能得到 Token 序列:[“我”, “有”, “一个”, “梦想”](具体取决于词表和算法)。每个 Token 会被查找映射到对应的ID,例如 “I” 可能对应 ID 40,” dream” 对应 ID 14324 等等(这里仅做示意)。分词的关键在于:模型只能接收固定词汇表内的 Token,如果输入中出现了不在词表的字串(比如生僻词),分词器会进一步把它拆成更小的已知片段或使用特殊的未知标记来处理。
经过分词和ID映射,我们就把文本成功地转换成了一串数字序列。接下来,需要将这些离散的数字进一步转换为计算方便的向量形式,这就是 Token 向量化。
Token 向量化:让计算机“理解”自然语言
得到 Token 的 ID 序列后,模型并不会直接拿这些 ID 数字进行计算。原因很简单:直接使用 ID(例如 14324 这样的数字)并没有任何语义上的意义——ID 之间的大小关系并不反映词语含义的关系。模型需要的是一种能够体现语义信息的表示方式。因此,LLM 的第一层通常是一个嵌入层(Embedding Layer),用于将每个 Token ID 映射为一个稠密的向量。这个过程我们称为 Token 向量化(Token Embedding)。
什么是嵌入向量? 简而言之,嵌入向量是用来表示词元的高维向量(通常数百维)。与简单的“单热(one-hot)”编码不同,Embedding 向量的每个维度都是连续值。这些向量是模型训练过程中学出来的,使得在向量空间中,语义相似的 Token 会有相似的向量表示 。换句话说,Embedding 把离散的符号(单词或字)映射到一个连续空间,使得模型可以通过数学运算来“理解”它们之间的关系 。例如,在一个训练良好的嵌入空间中,表示“国王”和“王后”的两个向量可能彼此接近,并且“国王-男人+女人”≈“王后”这样的关系可以通过向量运算近似体现出来,这正是嵌入向量捕捉语义的威力。
向量化的过程: 可以将嵌入层看作一个查表操作:它维护了一个形状为 (|V|, d) 的矩阵 ,其中 是词汇表大小,是嵌入维度(如 512, 768 或更高)。当一个 Token 的ID为 时,嵌入层输出的就是矩阵 的第 行向量 。比如,前述 “I have a dream” 的 Token 序列会被转换为一组向量 。这些向量往往是浮点数值,比如 [0.12, -0.45, …] 这样的形式,高维空间中每个向量可能无法直接直观理解,但模型可以利用它们进行后续计算。
经过嵌入层的处理,原本的文本已经变成了一个个可以被模型“消化”的向量。 正如 Hugging Face 的博文所述:这些嵌入向量序列才是神经网络真正的输入。有了它们,模型接下来就能在连续向量空间中对文本含义进行复杂的推理计算。这一步让计算机以数学形式“理解”了自然语言,为后续的推理奠定了基础。
模型推理:上下文理解与下一个词预测
当我们得到 Token 的向量序列后,这些向量将被送入大语言模型内部进行推理计算。当前主流的 LLM(如 GPT 系列、BERT 等)采用的都是 Transformer 架构。Transformer 由多层自注意力机制(self-attention) 和前馈神经网络组成,它能够高效地建模序列数据的长程依赖。虽然本篇焦点不在 Transformer 细节,但了解其作用有助于理解模型如何“思考”:
- 自注意力机制让模型可以在每一层动态关注序列中不同 Token 之间的关系。比如,当模型处理一句话时,注意力机制可以让词语“看到”句子中相关的其他词,从而结合上下文调整自身表示。
- 多层堆叠与非线性变换使模型逐步抽象出更高层次的语义表示。最初输入的是词嵌入向量,经过第一层注意力和前馈网络,得到的是结合了一定上下文的新的向量表示,随后一层层传播,语义信息被逐步汇聚、提炼。
经过 Transformer 多层计算后,模型最终在输出层得到对于下一个 Token的预测结果。具体来说,模型不会直接输出一个词或句子,而是输出一个对词汇表中每一个 Token 的评分,即通常所说的 logits 。可以将 logits 理解为“未归一化的分数”:分数高的 Token 更有可能是模型认为的下一个词,分数低的可能性则小。
举例来说,如果我们让模型接着输出 “I have a dream” 之后的内容,模型最后一层可能计算出像 “of”、”,”、”…” 等数万个 Token 各自的分数。假设其中 Token “of” 的得分很高。此时模型实际上已经完成了“下一词预测”的主要工作,但这些 logits 还需要转换成概率形式才能用于最终输出决策。**注意:**这种按词给出分数的过程会在每一步重复,每生成一个 Token 后,新 Token 会被加到输入中继续用于下一个 Token 的预测 。这就是自回归语言模型生成文本的基本方式:一步步预测下一个词的概率分布 。
概率分布与采样:模型如何选择下一个 Token
Softmax 转换概率: 模型得到每个 Token 的分数(logit)后,需要将其变为直观的概率。这是通过Softmax 函数实现的。Softmax 会考虑所有词的分数,将其指数化并归一化,得到一个概率分布,使所有可能 Token 的概率之和为 1 。Softmax公式一般表示为:
其中 就是模型认为 Token 作为下一个词的概率。例如,上述 “I have a dream” 的案例,如果 Softmax 计算得到 Token “of” 的概率为 17%,我们可记作: 。概率越高的 Token,自然是模型认为越符合上下文的续接词汇。
贪心还是随机? 有了下一词的概率分布,直觉上,我们可以选择其中概率最高的 Token 作为输出(这被称为贪心搜索或 Greedy Decoding)。贪心策略每一步都选择最可能的词,优点是简单直接,且如果我们设置每次都选最高概率,那么相同输入每次都会得到相同输出(即完全确定性的输出) 。乍一听这很好,但贪心策略往往会导致模型输出千篇一律甚至死板重复的内容 。试想如果一个人说话永远只选最可能的词,而不考虑变化,听起来会非常单调。同样地,大语言模型若总是选择概率最高的词,生成的文本可能缺乏创意,甚至陷入重复循环 。因此,在生成长文本时,我们通常希望引入适当的随机性,以获得更加多样和自然的结果。
引入随机性的采样策略: 为了让模型的输出更灵活多样,业界发展出多种采样(sampling)策略,在保证文本连贯的同时引入随机选择。主要策略包括 Temperature 温度系数、Top-k 采样和Top-p(核)采样等 。它们的核心思想都是:不每次都死板地选最大概率那个词,而是给概率排名靠前的几个候选词一个被选中的机会 。下面分别简要解释这些策略:
- 温度采样(Temperature): 温度参数会影响概率分布的“锐化”程度 。温度 在公式上表现为将 logits 除以 再送入 Softmax。 当 较高时(例如 是默认值),概率分布比较平滑,模型有较大机会选择到那些原本概率不高但非零的词;反之,当 很低时(趋近于0),Softmax 会使最高概率的那个词几乎垄断概率质量,输出基本等同于贪心选择 。因此,温度越高,输出越具有随机性和创造性;温度越低,输出越保守和确定 。通常,如果多次用相同 prompt 测试,会发现默认情况下模型每次回答可能都有差异;但若将温度设为0,那么每次都会得到完全相同的答案 。这是因为 实际上等效于总是选择概率最高的 Token(完全取消随机性)。
- Top-k 采样: Top-k 的思路是只考虑概率最高的 K 个候选 Token。模型原本给出了整个词表的概率分布,但很多词概率极低基本不可能,我们没必要管它们。通过设定一个 值,比如 ,我们截取最有可能的5个 Token,然后在它们之间按照相对概率随机抽取一个作为输出 。这样做可以确保我们只在模型比较有信心的几个词中挑选,从而避免那些概率尾部的奇怪词被选中,同时又比直接选最高的引入了一点变数。参数 K 的影响是: 大会给模型输出更多元的可能性,但也可能引入质量较差的词; 小则输出更稳定,但可能过于保守。极端地, 就退化为贪心解码(每次只选最可能的一个) 。
- Top-p 采样(又称核采样,Nucleus Sampling): Top-p 采样不是固定选取前多少个词,而是选取一个概率累积阈值 。它从最高概率开始累加,直到累积概率超过设定的 ,取这些累积范围内的所有候选作为集合 。换言之,Top-p 动态决定候选集合的大小,以确保这些候选词的总概率至少为 。例如,如果设定 ,模型可能需要取前两三个词(比如某次累计到15%时涵盖了“United”和“Netherlands”两个词 )作为候选,然后再在其中按相对概率随机选一个输出。Top-p 的优势在于:不管分布如何形状,它保证考虑足够概率质量的词。当分布很极端时,候选可能很少;当分布较平坦时,候选会相应变多。这样可以产生比 Top-k 更灵活的效果,也常用于提升生成文本的多样性 。
需要注意的是,以上策略可以结合使用。例如我们经常会同时设置一个较高的 (如 0.9)和一个适中的 (如 50),模型会先选出Top-k,再在其中应用Top-p限制,从而“双重过滤”。调节这些参数可以在文本质量和多样性之间取得平衡——过低的温度或 p 值会让输出非常确定但可能乏味,过高又可能让模型输出无关甚至荒谬的内容 。
同一输入为何输出不同? 基于以上采样机制,我们可以理解:如果引入了随机采样,每次生成时在高概率候选中可能抽到不同的词,因此即使提示完全相同,模型生成的回应也可能不同 。这并不意味着模型“不可靠”,而是生成式任务的正常现象——LLM 本质上生成的是概率驱动的一个合理答案,而非唯一确定的答案。只有在采用完全贪心且设置温度为0等极端确定性配置时,相同输入才会始终得到相同输出。事实上,ChatGPT 等对话系统之所以每次对同一问题可能给出略有差异的回答,就是因为其背后设置了温度采样等机制以保证对话不至于每次一模一样。这种随机性来源于模型推理阶段的采样策略,而并非模型在“记不住”之前的回答。
小示例:假设模型当前的上下文是 “今天的天气”,它预测下一个词可能是:“很好”(概率40%)、“不好”(概率30%)、“一般”(概率25%)、其他词(合计5%)。如果采取贪心策略,模型将选择概率最高的“很好”,输出句子可能是“今天的天气很好”。但如果使用采样策略,模型有一定几率选择“不好”或“一般”,从而可能输出“今天的天气不好”或者“今天的天气一般”。由此产生了不同的句子走向。这样的随机采样使得模型的输出更加丰富,不会千篇一律。当然,如果我们希望确保回答一致,可以将温度调低或固定随机种子来获得可重复的结果。
工程实践角度的说明
了解了上述原理后,我们来看在工程实践中如何应用这些知识。通常,我们会使用现有的深度学习框架和模型库(如 Hugging Face Transformers)来处理 Token 化和文本生成。这些库已经封装好了大部分细节,让我们可以方便地调用:
- 使用分词器(Tokenzier): 每个预训练的模型都会配套提供一个分词器。开发者需要先加载分词器,用它的 encode 或 call 方法将输入文本转换为 Token ID 列表。例如,在 Transformers 中:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("gpt2")
ids = tokenizer("I have a dream", return_tensors='pt').input_ids
这样就得到了对应的 Token ID 序列。实际应用中要确保使用与模型训练时相同的分词器和词表。
- Embedding 和模型前向: 对于用户而言,这一步通常是隐藏在模型内部的。我们加载模型后,将上一步得到的 Token IDs 张量输入模型,即可执行前向推理得到输出结果。以 Transformers 为例:
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("gpt2")
output = model.generate(input_ids=ids, max_new_tokens=50, temperature=0.8, top_p=0.9)
这里 generate 方法内部会处理模型的前向计算、Softmax 和采样策略等步骤。我们只需通过参数 temperature、top_p、top_k 等控制采样策略。例如,上述代码设置了温度0.8和 Top-p 0.9 来生成文本。如果将 temperature=0 和 top_p=1.0(或 do_sample=False)则会采用贪心方式生成确定性输出。
- 结果解码: 模型生成的是一系列 Token ID,我们需要用分词器的 decode 方法将它们转换回可阅读的文本 。例如:
result_text = tokenizer.decode(output[0], skip_special_tokens=True)
print(result_text)
得到模型最终生成的自然语言句子。
工程实践中还有一些值得注意的点:比如最大长度和结束标记的设置,以避免模型无限输出;截断或填充以适应模型的上下文窗口大小等等。这些超参数的选择可以显著影响模型性能和生成效果。实践时,常需要调参来找到较佳的输出质量与多样性平衡 。
结语
通过以上步骤解析,我们揭开了大语言模型从分词到向量化再到推理生成的完整过程。简而言之,LLM 的“思考”过程其实是:将输入转换成数字表示,在庞大的神经网络中基于训练所得的知识计算出下一词的概率分布,然后按照一定策略抽取输出,从而逐步生成文本答复。
这种生成机制使得模型能够产出连贯且富有语义的语言,但也决定了它本质上是一个概率模型。我们驳斥了“相同输入必定得到相同输出”的误解,理解了随机采样在生成中的作用。同时,我们也强调了嵌入向量对于模型“理解”语言的重要意义:没有恰当的向量表示,计算机无法以数学方式处理人类的语言信号。
对于开发者而言,理解这些原理有助于更好地调优模型行为。例如,知道如何调整 temperature 可以控制回答的随机性,了解分词机制可以避免不必要的长度增长或意外的 Token 切分。正如 Hugging Face 博客所言,大语言模型的神奇表现背后有着确定的逻辑流程和数学基础,我们可以既仰赖它强大的预训练知识,又通过工程手段精细地掌控它的输出。
大语言模型的发展仍在继续,从改进分词方法到更先进的采样策略,都有大量研究涌现。希望这篇文章为您提供了一个清晰的全景视角,帮助技术从业者更深入地理解 LLM 的工作原理,也为实际应用和调优提供一些指导。在未来,我们有理由相信,随着对这些机制理解的加深,我们将能构造出更高效、更智能的语言模型。
脱敏说明:本文所有出现的表名、字段名、接口地址、变量名、IP地址及示例数据等均非真实,仅用于阐述技术思路与实现步骤,示例代码亦非公司真实代码。示例方案亦非公司真实完整方案,仅为本人记忆总结,用于技术学习探讨。
• 文中所示任何标识符并不对应实际生产环境中的名称或编号。
• 示例 SQL、脚本、代码及数据等均为演示用途,不含真实业务数据,也不具备直接运行或复现的完整上下文。
• 读者若需在实际项目中参考本文方案,请结合自身业务场景及数据安全规范,使用符合内部命名和权限控制的配置。Data Desensitization Notice: All table names, field names, API endpoints, variable names, IP addresses, and sample data appearing in this article are fictitious and intended solely to illustrate technical concepts and implementation steps. The sample code is not actual company code. The proposed solutions are not complete or actual company solutions but are summarized from the author's memory for technical learning and discussion.
• Any identifiers shown in the text do not correspond to names or numbers in any actual production environment.
• Sample SQL, scripts, code, and data are for demonstration purposes only, do not contain real business data, and lack the full context required for direct execution or reproduction.
• Readers who wish to reference the solutions in this article for actual projects should adapt them to their own business scenarios and data security standards, using configurations that comply with internal naming and access control policies.版权声明:本文版权归原作者所有,未经作者事先书面许可,任何单位或个人不得以任何方式复制、转载、摘编或用于商业用途。
• 若需非商业性引用或转载本文内容,请务必注明出处并保持内容完整。
• 对因商业使用、篡改或不当引用本文内容所产生的法律纠纷,作者保留追究法律责任的权利。Copyright Notice: The copyright of this article belongs to the original author. Without prior written permission from the author, no entity or individual may copy, reproduce, excerpt, or use it for commercial purposes in any way.
• For non-commercial citation or reproduction of this content, attribution must be given, and the integrity of the content must be maintained.
• The author reserves the right to pursue legal action against any legal disputes arising from the commercial use, alteration, or improper citation of this article's content.Copyright © 1989–Present Ge Yuxu. All Rights Reserved.