ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

深入解析大模型基石:Token分词与Embedding向量化的原理与实践

深入解析大模型基石:Token分词与Embedding向量化的原理与实践 1. 项目概述从符号到空间的认知跃迁最近在跟一些刚入行的朋友聊大模型发现一个挺有意思的现象很多人对“Token”和“Embedding”这两个词耳熟能详但真要问它们到底是什么、怎么工作的、为什么是LLM的基石往往就语焉不详了。这就像开车只懂踩油门和刹车却不明白发动机和变速箱是怎么协同的一样一旦遇到复杂路况或者车子抛锚就完全束手无策了。今天我就想从一个一线实践者的角度把这两个核心概念掰开揉碎了讲清楚。这不仅仅是理论更是你理解模型微调、RAG检索、Agent推理乃至模型压缩等一切高级玩法的前提。我会结合具体的代码示例和工程中的“坑”让你不仅知道它们是什么更明白它们为什么这么设计以及在实际项目中如何正确地使用和调试它们。简单来说Token是LLM理解世界的“单词”而Embedding则是将这些“单词”转化为机器能进行数学计算的“思想向量”。没有Token化模型无法“阅读”文本没有Embedding模型无法“理解”和“推理”文本。这个过程本质上是在为人类模糊、离散的语言符号构建一个精确、连续、可计算的数学表示空间。2. Token大模型世界的“原子”如果把大模型比作一个超级大脑那么Token就是这个大脑处理信息的基本单位是构成所有思考和表达的“原子”。2.1 Token的本质与分词策略Token并不是简单的“单词”。在英文中一个Token可能是一个单词如“apple”也可能是一个标点如“.”或者是一个词根如“un-”。在中文里情况更复杂“清华大学”可能被分成“清华”和“大学”两个Token也可能作为一个整体Token这完全取决于分词器的训练数据和方法。目前主流的分词器Tokenizer基于Byte Pair Encoding (BPE)或其变种如WordPiece, SentencePiece。它的核心思想很巧妙从所有单个字符Byte开始统计语料中相邻字符对出现的频率将最高频的配对合并成一个新的“符号”不断迭代直到词表达到预定大小。这样得到的词表既能覆盖常见的单词和词组又能通过字符组合来处理从未见过的生僻词或拼写错误。举个例子假设语料中有“low”、“lower”、“newest”、“widest”。BPE算法可能会先合并高频的“e”和“s”成为“es”然后合并“es”和“t”成为“est”最终“est”成为一个独立的Token。这样“lowest”虽然不在原始词表中但可以被拆分为“low”和“est”两个已知Token来处理。注意选择不同的分词器如OpenAI的cl100k_baseMeta的sentencepiece或Hugging Face的BertTokenizer会对同一段文本产生不同的Token序列。这直接影响到模型的理解、生成文本的长度计算Token计数以及API调用成本。例如中文用cl100k_base分词“你好”可能是一个Token而用某些开源分词器可能是两个。2.2 Token化的工程实践与常见陷阱在实际调用API或使用开源模型时Token化是隐式发生的但理解其过程对调试至关重要。# 使用OpenAI的tiktoken库进行分词示例 import tiktoken # 指定编码对应不同的模型如gpt-4使用cl100k_base enc tiktoken.get_encoding(cl100k_base) text 大模型LLM正在改变世界。 tokens enc.encode(text) print(Token IDs:, tokens) print(Tokens:, [enc.decode_single_token_bytes(t) for t in tokens]) # 输出可能类似 # Token IDs: [65301, 234, 12345, ...] # Tokens: [b\xe5\xa4\xa7, b\xe6\xa8\xa1\xe5\x9e\x8b, b\xef\xbc\x88, ...] # 计算Token数量用于估算API成本 num_tokens len(tokens) print(f文本的Token数量: {num_tokens})这里有几个必须知道的坑Token数量不等于字数或单词数中文、日文、韩文等语言一个字可能由多个字节组成BPE算法可能会将其切分成多个Token。一个复杂的Emoji表情甚至可能占用多个Token。因此永远不要用字符串长度来估算Token消耗和API成本必须使用对应模型的分词器进行精确计算。上下文窗口限制的本质模型的上下文长度如128K限制的是Token的数量而非字符数。如果你的文本经过分词后Token数超出限制模型将无法处理。需要采用滑动窗口、总结或选择性丢弃等策略。特殊Token的影响除了文本Token分词器还会引入一些特殊Token如|endoftext|文本结束、[CLS]分类标志、[SEP]分隔符等。这些Token也会占用上下文窗口并在模型中有特殊含义在拼接提示词Prompt时需要留意。我曾在一次处理长文档问答时因为直接用字符串切片来截断文档以满足“字符数”限制导致截断点正好在一个中文字符的中间后续的分词完全混乱模型输出了大量乱码。教训就是所有基于长度的截断、切片操作都必须在Token化之后进行而不是在原始文本上进行。3. Embedding从离散符号到连续空间如果说Token化让模型有了“视力”可以看见文字那么Embedding就是让模型有了“理解力”能够把握文字背后的含义。它的目标是将高维、稀疏的One-hot Token表示一个巨大的向量只有对应Token位置是1其余全是0映射到一个相对低维、稠密、连续的向量空间中。3.1 Embedding的核心思想与数学原理想象一下我们有一个包含5万个单词的词表。传统的One-hot编码中“国王”这个词可能是[0,0,1,...,0]“皇后”是[0,1,0,...,0]。这两个向量是正交的点积为0从数学上看它们毫无关系。但这显然不符合我们的认知“国王”和“皇后”在语义上是相关的。Embedding层通常是一个可训练的查找矩阵解决了这个问题。这个矩阵的大小是[词表大小V, 嵌入维度D]。当输入一个Token ID为i时我们就在这个矩阵的第i行取出一个D维的向量作为它的嵌入表示。通过在海量文本数据如“国王 - 男人 女人 ≈ 皇后”上训练模型会调整这个矩阵中的数值使得语义相近的Token在向量空间中的位置即嵌入向量也接近。衡量“接近”的常用方法是计算余弦相似度或欧氏距离。# 一个简化的Embedding查找过程概念示例 import torch vocab_size 50000 # 词表大小 embedding_dim 768 # 嵌入维度例如BERT-base的配置 # 初始化Embedding矩阵在真实模型中此矩阵由训练得到 embedding_layer torch.nn.Embedding(vocab_size, embedding_dim) # 假设“国王”、“皇后”、“男人”、“女人”的Token ID token_ids torch.tensor([1024, 2048, 3072, 4096]) # 示例ID # 获取它们的嵌入向量 token_embeddings embedding_layer(token_ids) print(token_embeddings.shape) # 输出: torch.Size([4, 768]) # 计算“国王 - 男人 女人”的向量 queen_vector token_embeddings[0] - token_embeddings[2] token_embeddings[3] # 理论上queen_vector应该与token_embeddings[1]“皇后”的向量最相似 # 可以通过余弦相似度来验证 from torch.nn.functional import cosine_similarity similarity cosine_similarity(queen_vector.unsqueeze(0), token_embeddings[1].unsqueeze(0)) print(f与‘皇后’向量的余弦相似度: {similarity.item():.4f})3.2 上下文嵌入静态与动态之别早期的Word2Vec、GloVe产生的词嵌入是静态的。无论“苹果”出现在“吃苹果”还是“苹果公司”的语境中它的向量表示是固定的。这显然有局限。现代Transformer-based LLM如BERT、GPT系列使用的是上下文嵌入。同一个Token在不同句子中会有不同的向量表示。这是通过模型的自注意力机制实现的。模型在处理一个句子时会根据该Token周围的所有其他Token来动态调整其最终的表示即最后一层隐藏状态。因此“银行”在“存入银行”和“河岸边的银行”中会得到两个语义截然不同的嵌入向量。这是LLM理解力飞跃的关键。在生成式模型如GPT中我们通常取模型最后一层隐藏状态Hidden States作为Token的上下文嵌入。在编码器模型如BERT中我们可能会取所有层输出的平均值或者最后一层[CLS]Token的向量作为整个句子的表示。3.3 Embedding的实战应用以RAG为例Embedding最经典的应用莫过于检索增强生成RAG。其核心步骤就是将知识库文档和用户问题都转化为嵌入向量然后通过向量相似度搜索找到最相关的文档片段。# 一个简化的RAG检索环节示例使用sentence-transformers库 from sentence_transformers import SentenceTransformer import numpy as np from sklearn.metrics.pairwise import cosine_similarity # 1. 加载一个高效的Embedding模型如BGE model SentenceTransformer(BAAI/bge-base-zh-v1.5) # 2. 知识库文档假设已分块 knowledge_chunks [ 大语言模型LLM是基于Transformer架构的深度学习模型。, Embedding是将文本转化为数值向量的技术。, RAG通过检索外部知识来增强LLM的生成能力。 ] # 3. 为知识库生成嵌入向量并存储 chunk_embeddings model.encode(knowledge_chunks, convert_to_tensorTrue) # 4. 用户查询 query 如何让大模型获取外部知识 query_embedding model.encode(query, convert_to_tensorTrue) # 5. 计算相似度并检索 # 将PyTorch Tensor转换为numpy数组以便计算 cos_scores cosine_similarity(query_embedding.cpu().numpy().reshape(1, -1), chunk_embeddings.cpu().numpy())[0] # 找到最相关的文档索引 top_k_idx np.argsort(cos_scores)[-1:][::-1] # 取最相关的1个 print(f用户查询: {query}) for idx in top_k_idx: print(f相关文档 (相似度: {cos_scores[idx]:.4f}): {knowledge_chunks[idx]})在这个流程中选择合适的Embedding模型至关重要。text-embedding-ada-002OpenAI、BGE系列智源、M3E等是常见选择。你需要考虑语义匹配能力在你的领域数据上表现如何向量维度维度越高通常表征能力越强但存储和计算成本也越高。1536维ada-002和768维BGE-base是常见尺寸。序列长度模型能处理的最大Token长度如512、1024、2048这决定了你的文本分块策略。计算效率本地部署时的推理速度。实操心得在构建生产级RAG系统时不要只依赖余弦相似度。可以考虑重排序技术即先用一个快速的、召回率高的Embedding模型如BGE-M3从海量文档中粗筛出Top N如100个候选片段再用一个更精细但更慢的模型如text-embedding-3-large或交叉编码器对这N个片段进行精排选出最相关的Top K如3个送入LLM生成答案。这能在成本和效果间取得很好平衡。4. 全流程串联从原始文本到模型理解现在我们把Token和Embedding串起来看一个文本在输入LLM前究竟经历了什么。4.1 端到端的数据处理流水线假设我们有一句用户查询“解释一下注意力机制。”文本规范化清理文本如统一大小写、去除多余空格、处理特殊字符。“解释一下注意力机制。”-“解释一下注意力机制。”Token化使用模型对应的分词器。使用cl100k_baseGPT-4可能被分词为[“解释” “一下” “注意” “力” “机制” “。”]对应ID[65301, 234, 567, 890, 1234, 13]。这里“注意力”被拆成了“注意”和“力”这是BPE分词在中文上的一个特点。添加特殊Token根据模型格式要求添加系统提示、用户标识等。对于ChatGPT API可能包装成[{role: user, content: 解释一下注意力机制。}]然后内部再转换为Token序列并添加|im_start|,|im_end|等特殊Token。转换为嵌入向量模型内部的Embedding层将每个Token ID如65301转换为其对应的初始向量如一个768维的向量。此时还是静态词嵌入。位置编码注入为了让模型感知Token的顺序会为每个位置生成一个位置编码向量与Token嵌入向量相加。这是Transformer理解序列的关键。上下文编码模型前向传播带有位置信息的嵌入向量序列输入Transformer层。通过自注意力机制每个Token的向量都会与序列中所有其他Token的向量进行交互最终输出包含了完整上下文信息的动态嵌入向量即每个Token对应的最后一层隐藏状态。输出头处理对于生成任务模型会基于最后一个Token的上下文嵌入向量预测下一个Token的概率分布然后进行采样生成下一个Token如此循环。4.2 关键参数与配置解析在整个流程中有几个参数深刻影响着模型的行为和资源消耗max_length/max_new_tokens控制生成文本的最大长度Token数。设置过小可能导致回答被截断设置过大会浪费计算资源并可能生成冗余内容。temperature控制生成随机性的参数。值越高如0.8-1.0输出越多样、有创意值越低如0.1-0.3输出越确定、保守。对于事实性问答通常用低温度对于创意写作可用高温度。top_p(核采样)另一种控制随机性的方法。它从累积概率超过阈值p的最小Token集合中采样。通常与temperature结合使用可以避免采样到概率极低的奇怪Token。理解这些参数你才能像调试程序一样去调试LLM的输出。5. 高级话题与性能优化掌握了基础我们可以看看更深入的问题和优化手段。5.1 长上下文与效率瓶颈当处理长文本如整本书、长对话记录时Token数量激增带来两个核心问题计算复杂度Transformer的自注意力机制复杂度是O(n²)n是Token数。当n从1k增加到100k时计算量呈万倍增长显存消耗巨大。信息稀释在超长序列中关键信息可能被淹没模型难以有效关注到相关内容。目前的解决方案包括滑动窗口注意力只计算每个Token与附近局部窗口内Token的注意力降低复杂度。稀疏注意力/线性注意力设计更高效的注意力计算方式如Longformer、BigBird中使用的技术。层次化处理先将长文本分块分别生成摘要或嵌入再进行聚合分析。检索增强这正是RAG的核心思想不把所有信息都塞进上下文而是动态检索相关的部分。5.2 Embedding模型的选型与微调市面上有大量开源的Embedding模型如何选择模型名称发布方主要特点适用场景text-embedding-3-small/largeOpenAI性能强劲有不同尺寸可选支持维度缩减。通用语义检索效果标杆需调用API。BGE (BAAI General Embedding)智源AI中文优化好开源有不同尺寸版本。中文场景RAG开源项目首选。M3E (Moka Massive Mixed Embedding)MokaAI在中文文本匹配和检索任务上表现突出。中文段落对匹配、相似度计算。E5 (Embeddings from bidirEctional Encoder rEpresentations)微软通过指令微调在检索任务上表现优异。需要区分查询和文档的检索系统。Sentence-BERTUKPLab基于BERT的双塔编码器历史悠久生态丰富。学术研究、语义相似度计算。如果你的领域非常垂直如医疗、法律、金融通用Embedding模型可能无法准确捕捉专业术语的语义。这时就需要领域微调。你可以收集领域内的文本对如问题-答案、相关文档片段构造相似/不相似样本用对比学习如InfoNCE损失来微调预训练的Embedding模型使其向量空间更贴合你的领域。5.3 Token与Embedding的关联调试技巧在实际开发中很多问题源于对这两个概念关联处的理解不足。问题一为什么我计算出的Token数和API返回的用量对不上可能原因你用的分词器版本或编码与API后端不一致。你忽略了系统提示词、角色标识等特殊Token。API可能对输入进行了额外的预处理如规范化。排查方法用API提供商官方推荐的库如OpenAI的tiktoken重新计算并对比简单Prompt和复杂Prompt的消耗差异。问题二RAG检索效果差找不到相关文档。可能原因分块策略不当块太大包含无关信息稀释了核心语义块太小丢失了完整上下文。需要根据文档结构段落、标题和语义完整性进行分块。Embedding模型不匹配通用模型对专业术语不敏感。检索相似度算法单一仅用余弦相似度可能不够可以考虑结合BM25等关键词匹配方法进行混合检索。查询改写原始用户问题可能太简短或模糊可以用一个轻量级LLM先对查询进行扩展或改写再用改写后的查询去检索。排查方法构建一个小的测试集人工标注查询与文档块的相关性。然后检查Top K检索结果中相关文档的排名。如果排名靠后分析是分块问题还是Embedding模型问题亦或是查询本身表述问题。理解Token和Embedding就像掌握了内功心法。之后无论学习提示工程、微调、RAG还是Agent你都能一眼看穿其底层运作机制快速定位问题设计出更优雅高效的解决方案。这不仅仅是两个技术概念更是你与LLM这座“黑箱”进行有效对话的桥梁。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进