1. 从“字”到“词”为什么大模型的世界里没有“你好”如果你刚开始接触大模型可能会被一个看似简单的问题绊住为什么我输入“你好世界”模型返回的文本长度Token数不是4而是5、6甚至更多为什么我精心设计的提示词有时会被模型“曲解”这一切的根源都始于一个最基础、也最容易被忽视的概念Token。很多人把Token简单理解为“单词”这是第一个认知误区。在中文大模型的世界里尤其是像ChatGPT、文心一言、通义千问这类基于Transformer架构的模型Token是模型“看见”和“理解”世界的最小单位。它不是我们人类理解的“字”或“词”而是一种经过复杂算法切割后的文本片段。这个切割过程就是分词Tokenization。让我用一个最直接的例子来说明。假设我们有一个极其简单的英文句子“I love apple pie.”。作为人类我们自然将其分为四个单词[“I”, “love”, “apple”, “pie.”]。但对于大模型的分词器Tokenizer来说情况可能完全不同。以OpenAI的cl100k_baseGPT-4/3.5-turbo等模型使用分词器为例这句话可能会被切分成[“I”, “ love”, “ apple”, “ pie”, “.”]。看到了吗“pie”和句号被分开了而且“love”和“apple”前面多了一个空格在分词中空格经常被当作一个独立字符或与前一个词合并处理。这个列表中的每一个元素如“I”、“ love”、“ pie”就是一个Token。现在把场景切换到中文。“你好世界”这四个字用不同的分词器处理结果天差地别按字分词[“你”, “好”, “世”, “界”]- 4个Token。这是最朴素的方式但会丢失“你好”、“世界”这些词的语义信息。按词分词如jieba[“你好”, “世界”]- 2个Token。更符合人类直觉但“你好”可能在某些语境下被拆开如“你的好处”。大模型常用分词器如BERT的WordPieceGPT的BPE很可能被切成[“你”, “好”, “世”, “界”]或[“你”, “好”, “世界”]。为什么因为大模型的分词器是在海量语料上通过统计学习“学”出来的目标是找到一种平衡既能用有限的词汇表例如5万到10万个Token覆盖绝大多数文本又能让每个Token尽可能承载有意义的语义。对于它来说“世界”可能是一个常见的组合所以成为一个Token“你好”虽然常见但“你”和“好”作为独立Token的出现频率极高组合成新Token的收益不大所以可能被分开。理解这一点至关重要。Token是模型与文本交互的“货币”和“接口”。我们输入的提示词Prompt、模型生成的每一个字都需要先兑换成Token在模型的内部进行计算。Token数量的多少直接关系到API调用成本按Token计费、模型的处理效率有上下文窗口限制如128K Tokens以及最关键的——模型对语义的理解精度。一个糟糕的分词可能会让“武汉市长江大桥”被理解成“武汉市长 江大桥”从而彻底歪曲后续的生成逻辑。所以别再问“这个模型的中文理解能力如何”这种空泛的问题了。一个更精准的切入点是这个模型使用的分词器对中文的切割方式是否高效、是否合理这才是真正入门大模型的第一课。2. 分词器大模型如何“学会”阅读——BPE/WordPiece算法深度拆解既然Token如此重要那么大模型究竟是如何“学会”将连续文本切成一个个Token的呢这背后主要是两种核心算法BPEByte Pair Encoding字节对编码和WordPiece。它们听起来复杂但核心思想可以用一个“合并游戏”来理解。想象你有一本巨大的、包含所有互联网文本的书。最开始你的词汇表里只有最基本的“字母表”比如英文的26个字母加上一些标点符号中文的几千个单字。你的目标是通过不断合并出现频率最高的“符号对”来构建一个规模固定比如5万个的新词汇表这个词汇表里的每一个元素就是一个Token。2.1 BPE算法从字母到“词根”的进化史BPE的过程非常直观初始化将训练语料中的所有文本拆分成最小的单元。对于英文就是字母和常见符号对于中文可以是单字Character。统计与合并扫描整个语料库找出出现频率最高的一对相邻符号。比如“e”和“s”经常连在一起出现如“yes”“best”那么就把“es”合并成一个新的符号加入词汇表。迭代重复步骤2不断合并最高频的符号对直到词汇表达到我们预设的大小比如5万次合并后词汇表有5万基础符号个条目。这个过程就像生物进化。一开始只有氨基酸字母然后氨基酸组合成短肽高频字母对如“th”“ing”短肽再组合成蛋白质更长的子词如“ation”“ness”。最终形成的词汇表里既包含完整的常见单词如“the”“cat”也包含大量的词根、词缀和常见词组如“ized”“pre-”“-able”。OpenAI的GPT系列包括ChatGPT主要使用BPE的变体。它的一个关键特点是将空格视为一个普通字符。这就是为什么在GPT的Token中你经常看到像“ love”前面带空格这样的Token。因为空格和“love”在语料中频繁相邻出现所以被合并了。这带来了一个好处模型能明确感知到单词的边界对于英文这种以空格分隔单词的语言处理起来更高效。2.2 WordPiece算法BPE的“概率升级版”WordPiece是Google为BERT模型提出的核心思想与BPE类似但合并策略从“频率最高”变成了“能最大程度提升语言模型概率”的一对。简单来说它不仅仅看两个符号是否经常一起出现还要计算如果把这两个符号合并成一个新符号用这个新符号重新表示整个语料库那么语料库作为一个整体的出现可能性似然概率提升了多少它选择能带来最大概率提升的那一对进行合并。这听起来有点绕但你可以理解为一种更“聪明”的合并。它倾向于合并那些组合后语义更稳定、更不可分割的片段。BERT、ALBERT等模型使用WordPiece。对于中文BERT通常直接以字为单位进行WordPiece学习所以它的中文词汇表看起来就是一堆单字加上一些常见双字词如“中国”、“经济”。2.3 实战观察不同分词器的差异我们可以用代码直观感受一下。假设我们使用transformers库一个主流的开源NLP库from transformers import AutoTokenizer # 加载不同的分词器 tokenizer_gpt2 AutoTokenizer.from_pretrained(gpt2) # 使用BPE tokenizer_bert AutoTokenizer.from_pretrained(bert-base-chinese) # 使用WordPiece text 深度学习模型正在理解人类的语言。 tokens_gpt2 tokenizer_gpt2.tokenize(text) tokens_bert tokenizer_bert.tokenize(text) print(GPT-2 (BPE) Tokens:, tokens_gpt2) print(BERT (WordPiece) Tokens:, tokens_bert)输出可能会类似于GPT-2 (BPE) Tokens: [深, 度, 学, 习, 模, 型, 正, 在, 理, 解, 人, 类, 的, 语, 言, 。] BERT (WordPiece) Tokens: [深, 度, 学, 习, 模, 型, 正, 在, 理, 解, 人, 类, 的, 语, 言, 。]注由于示例模型和具体词汇表版本结果可能不同但中文上两者常都退化为字级别分词而对于英文句子unhappilyBPE可能会切成[“un”, “happ”, “ily”]因为它学到了“un”这个前缀“happ”这个根和“ily”这个后缀。WordPiece可能会切成[“un”, “##hap”, “##pily”]##表示子词前缀它更强调合并后整体的概率增益。关键心得选择模型时了解其底层分词器对中文的处理粒度至关重要。如果你的任务涉及大量专业术语、成语或新兴网络用语一个以“字”为主的分词器可能会丢失关键语义关联导致模型表现不佳。此时可能需要寻找针对中文优化过、包含更多常见词汇的分词器模型如一些国产大模型或者考虑在输入前对文本进行预处理。3. 从Token到语义Embedding如何让文字拥有“向量灵魂”Token只是离散的符号。计算机包括神经网络无法直接理解“苹果”这个词代表一种水果。它们只认识数字。那么如何将“苹果”这个Token转换成模型能处理的数字形式呢第一步是索引化词汇表中的每个Token都有一个唯一的ID。比如“苹果”可能对应ID 12345。但ID 12345和ID 12346比如“香蕉”之间除了数字大小没有任何语义关系。这显然不行。我们需要一种方法让“苹果”和“香蕉”的表示既不同因为它们是不同的水果又相似因为它们都是水果并且与“汽车”的表示相差甚远。这就是Embedding嵌入/向量化登场的时候。它的核心思想是为词汇表中的每一个Token学习一个固定长度的、稠密的实数向量例如一个长度为768或1024的数组。这个向量不是随机的而是在模型训练过程中根据这个Token在数十亿甚至数万亿文本中出现的上下文不断调整优化而来的。3.1 Embedding的本质高维空间中的语义地图你可以把Embedding向量想象成在一个非常高维比如768维的空间中的一个点。这个空间就是模型的“语义空间”。在这个空间里语义相似的词距离相近。“国王”和“王后”的向量点会很靠近“苹果”和“香蕉”也会比较靠近但可能位于“水果”这个更大的聚类区域内。语义关系可以向量运算。经典的例子是vec(“国王”) - vec(“男人”) vec(“女人”) ≈ vec(“王后”)。这说明模型捕捉到了“性别”这种抽象关系。上下文改变位置。同一个词在不同语境下可以有不同的向量通过模型的上下文编码层实现如Transformer但它的基础Token Embedding提供了一个起始的“锚点”。3.2 如何得到Embedding在大模型如GPT、LLaMA中Embedding通常是一个可学习的查找表Lookup Table也叫Embedding Layer。它的行数等于词汇表大小V列数等于隐藏层维度D。当我们输入一个Token ID如12345时模型就从这个表中取出第12345行的那个D维向量作为这个Token的初始表示。这个查找表的值是在预训练阶段通过掩码语言模型MLM或自回归语言模型任务学到的。模型的任务是预测被掩盖的单词为了做好预测它必须让每个Token的向量能够很好地与其他Token的向量协作从而在整体上反映出复杂的语义和语法规律。3.3 实战获取并使用Embedding我们以使用sentence-transformers库获取文本的Embedding为例它封装了生成高质量句子向量的模型如BGE、all-MiniLM-L6-v2。from sentence_transformers import SentenceTransformer # 加载一个轻量级的Embedding模型 model SentenceTransformer(all-MiniLM-L6-v2) # 准备文本 sentences [我喜欢吃苹果, 香蕉是一种热带水果, 今天天气真好] # 生成Embedding向量 embeddings model.encode(sentences) print(f句子数量: {len(embeddings)}) print(f每个句子的向量维度: {embeddings[0].shape}) # 通常是384维 print(f前10个维度值示例: {embeddings[0][:10]}) # 计算句子间的余弦相似度 from sklearn.metrics.pairwise import cosine_similarity similarity_matrix cosine_similarity(embeddings) print(\n句子间相似度矩阵:) print(similarity_matrix)运行后你会得到三个句子的向量每个都是384个浮点数以及它们之间的相似度。你会发现“我喜欢吃苹果”和“香蕉是一种热带水果”的相似度会高于它们各自与“今天天气真好”的相似度因为前两句都关于“水果”。核心避坑点encode方法内部已经包含了分词Tokenization和向量化Embedding两个步骤。它首先用其内置的分词器将句子切成Token然后将这些Token的向量通过模型通常是Transformer的池化层聚合成一个固定的句子向量。所以我们通常无需手动处理单个Token的Embedding除非你在做非常底层的模型研究或定制。对于大多数应用搜索、聚类、检索直接使用成熟的句子Embedding模型是最高效的选择。4. 语义搜索实战用Embedding构建你的私人知识库助手理解了Token和Embedding的原理我们现在可以做一个非常实用且强大的项目基于语义的本地知识库问答系统。它的核心思路是将你的私人文档如PDF、TXT、Markdown转换成Embedding向量存储起来当用户提问时将问题也转换成向量然后在向量数据库中快速找到与问题语义最相关的文档片段最后将这些片段作为上下文交给大模型如GPT生成精准的答案。这个架构通常被称为RAGRetrieval-Augmented Generation检索增强生成。它完美结合了Embedding的语义理解能力和大模型的生成能力既能利用外部知识又能避免大模型的“幻觉”胡编乱造。4.1 系统架构与工具选型文档加载与切分我们需要将长文档切成适合处理的小块Chunks。因为Embedding模型有输入长度限制通常512或1024个Token。切分时要注意保持语义完整性比如按段落、按标题切分避免从句子中间切断。工具推荐LangChain的RecursiveCharacterTextSplitter或MarkdownTextSplitter。它们能根据字符、分隔符递归切分并保持一定的重叠窗口防止上下文断裂。文本向量化将切分后的文本块转化为Embedding向量。模型推荐BGEBAAI/bge-base-zh或GTEthenlper/gte-base-zh。这些都是针对中文优化的、开源的、效果很好的文本嵌入模型。sentence-transformers库完美支持。向量存储与检索存储向量并实现基于余弦相似度的快速检索。工具推荐轻量级首选Chroma功能全面可选FAISSFacebook AI Similarity Search。它们都是内存或本地磁盘向量数据库无需复杂部署。大模型集成将检索到的相关文本块组合成提示词Prompt发送给大模型生成答案。选择可以使用OpenAI API、国内大模型API如文心、讯飞或本地部署的开源模型如ChatGLM、Qwen。4.2 分步实现代码详解我们以处理一组Markdown格式的技术文档为例构建一个本地问答系统。# 步骤1安装必要库 # pip install langchain langchain-community sentence-transformers chromadb pypdf import os from langchain_community.document_loaders import DirectoryLoader, TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.embeddings import HuggingFaceEmbeddings from langchain.vectorstores import Chroma from langchain.chains import RetrievalQA from langchain.llms import OpenAI # 示例用OpenAI可替换为其他LLM from langchain.prompts import PromptTemplate import getpass # 步骤2加载文档假设文档在 ./docs 目录下 documents [] for root, dirs, files in os.walk(./docs): for file in files: if file.endswith(.md) or file.endswith(.txt): path os.path.join(root, file) loader TextLoader(path, encodingutf-8) documents.extend(loader.load()) print(f已加载 {len(documents)} 个文档) # 步骤3切分文本 text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 每个块的最大字符数可根据Embedding模型调整 chunk_overlap50, # 块之间的重叠字符数防止信息割裂 separators[\n\n, \n, 。, , , , , , ] # 中文优先分隔符 ) chunks text_splitter.split_documents(documents) print(f切分为 {len(chunks)} 个文本块) # 步骤4初始化Embedding模型 # 使用BGE中文模型devicecuda如果有GPU embedding_model HuggingFaceEmbeddings( model_nameBAAI/bge-base-zh-v1.5, model_kwargs{device: cpu}, encode_kwargs{normalize_embeddings: True} # 归一化方便余弦相似度计算 ) # 步骤5创建向量数据库 # persist_directory 指定持久化目录下次可直接加载 vector_db Chroma.from_documents( documentschunks, embeddingembedding_model, persist_directory./chroma_db ) vector_db.persist() # 保存到磁盘 print(向量数据库已创建并持久化。) # 步骤6设置大模型这里需要你的API Key # 注意生产环境请使用环境变量管理密钥不要硬编码 os.environ[OPENAI_API_KEY] getpass.getpass(请输入你的OpenAI API Key: ) llm OpenAI(model_namegpt-3.5-turbo, temperature0.1) # temperature调低答案更确定 # 步骤7创建检索问答链 prompt_template 请根据以下上下文信息回答问题。如果上下文信息不足以回答问题请直接说“根据提供的信息我无法回答这个问题”不要编造答案。 上下文 {context} 问题{question} 请给出专业、准确的回答 PROMPT PromptTemplate( templateprompt_template, input_variables[context, question] ) qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # 最简单的方式将所有检索到的上下文拼接到Prompt中 retrievervector_db.as_retriever(search_kwargs{k: 3}), # 检索最相关的3个块 chain_type_kwargs{prompt: PROMPT}, return_source_documentsTrue # 返回来源文档便于溯源 ) # 步骤8进行问答 query 什么是Transformer模型中的注意力机制 result qa_chain({query: query}) print(f问题{query}) print(f答案{result[result]}) print(\n--- 来源文档片段 ---) for i, doc in enumerate(result[source_documents]): print(f[片段{i1}]: {doc.page_content[:200]}...) # 打印前200字符4.3 关键细节与调优经验分块策略是成败关键chunk_size不是越大越好。太大可能包含无关信息稀释核心内容太小则可能丢失完整语义。对于技术文档500-1000字符是个不错的起点。chunk_overlap设置50-150字符能有效缓解边界信息丢失问题。Embedding模型选择bge-base-zh在中文通用任务上表现均衡。如果你的文档领域特殊如医学、法律可以尝试在领域数据上微调Embedding模型或寻找领域专用的模型。检索数量kk3或k4通常是个安全的选择。太少可能信息不全太多则可能引入噪声并增加Prompt长度消耗更多Token和费用。可以通过少量测试问题来调整。Prompt工程示例中的Prompt模板虽然简单但明确指令“不要编造答案”对于减少幻觉至关重要。你还可以在Prompt中加入角色设定“你是一个技术专家”、输出格式要求等以进一步控制生成质量。成本与性能权衡使用云API大模型如GPT-4生成答案质量高但持续产生费用。对于内部知识库可以考虑用检索到的文档片段作为上下文驱动一个较小的、本地部署的开源模型如Qwen-7B-Chat来生成答案实现完全本地化、零成本的解决方案。这个实战项目将Token、分词、Embedding、向量检索和大模型生成串联了起来构成了当前AI应用开发中最核心的范式之一。通过动手实现它你不仅能深刻理解这些概念如何协同工作更能获得一个极具实用价值的工具雏形。5. 避坑指南Token与Embedding应用中的高频陷阱在实际开发和调试中仅仅理解原理是不够的更重要的是识别和避开那些常见的“坑”。以下是我在项目中反复遇到的一些典型问题及其解决方案。5.1 Token计数不准导致的预算超标或截断问题调用按Token计费的API如OpenAI时自己估算的Token数和实际消耗差异巨大导致费用超支。或者输入的提示词因超出模型上下文窗口而被静默截断导致模型回答不完整。根因错误地以“字数”或“单词数”估算Token。中英文混合时尤其不准。忽略了不同模型使用不同的分词器。GPT-3.5、GPT-4、Claude、LLaMA的分词方式都不同。提示词中的系统指令、用户消息、助理消息的格式如ChatML格式本身也会占用Token。解决方案必须使用官方或准确的分词工具进行计数。对于OpenAI系列使用tiktoken库。import tiktoken encoding tiktoken.encoding_for_model(gpt-4) # 指定模型 tokens encoding.encode(你的提示词在这里) print(fToken数量: {len(tokens)}) # 也可以解码查看具体分词结果 print([encoding.decode_single_token_bytes(token).decode(utf-8, errorsreplace) for token in tokens[:10]])在构造复杂提示词时实时计算Token数。特别是在实现“动态上下文”或“历史对话”功能时务必在添加每轮对话前检查总Token数是否接近上限如128K的90%并制定裁剪策略如优先移除最早的历史记录。了解模型的上下文窗口和计价方式。例如GPT-4 Turbo是128K上下文但输入和输出分开计价。长上下文虽然方便但费用也更高需权衡。5.2 中文Embedding效果不佳语义相似但向量距离远问题用开源的Embedding模型处理中文句子对时明明语义很相似但计算出的余弦相似度却很低例如低于0.5。根因模型未针对中文优化许多优秀的Embedding模型如all-MiniLM-L6-v2主要基于英文语料训练对中文的语义空间映射不佳。文本预处理不一致比如一句做了去除停用词、标点另一句没有导致向量表征的基础就不同。领域不匹配通用Embedding模型在处理特定领域如金融、生物文本时难以捕捉领域内特有的语义关联。解决方案换用或微调中文SOTA模型优先选择BAAI/bge-*zh*、moka-ai/m3e-base、GTE等明确针对中文优化的模型。它们是当前中文Embedding任务的事实标准。标准化预处理流程对所有文本采用相同的清洗步骤如统一转小写、去除特殊字符但保留中文标点等。对于检索任务有时保留停用词反而更好因为它们可能携带语气或逻辑信息。领域微调进阶如果拥有大量领域标注数据句子对和相似度分数可以在预训练的中文Embedding模型基础上进行微调让模型更好地适应你的数据分布。Hugging Face的sentence-transformers库提供了完善的训练接口。5.3 向量检索返回无关内容检索精度低问题RAG系统中用户的问题很明确但向量数据库返回的前几个文本块却完全不相关。根因分块策略不当这是最常见的原因。块太大包含了多个主题噪声淹没了信号块太小语义不完整Embedding无法准确表征。检索方式单一仅使用简单的“向量相似度”检索可能无法处理关键词完全匹配但语义不同或语义相同但表述迥异如“怎么做” vs “如何实现”的情况。Embedding模型“偏见”模型可能对某些类型的表述或领域存在系统性偏差。解决方案迭代优化分块这是最重要的步骤。尝试不同的chunk_size和chunk_overlap并用一批典型问题测试检索召回率。对于结构化工整的文档如API文档可以尝试按标题###进行分割。采用混合检索Hybrid Search结合稠密向量检索语义相似和稀疏向量检索关键词匹配如BM25。Chroma和Weaviate等向量库已支持混合检索。这能同时保证语义的灵活性和关键词的精确性。重排序Re-ranking先通过向量检索召回较多的候选结果如k20然后使用一个更精细的、专门用于判断相关性的**交叉编码器Cross-Encoder**模型对这20个结果进行重排序只取Top-3给大模型。虽然多了一步但精度提升显著。sentence-transformers也提供了Cross-Encoder模型。查询扩展在检索前先用大模型对原始用户问题进行改写或扩展生成多个同义或相关的查询语句分别进行检索然后合并结果。这能提高召回率。5.4 Embedding服务报错No embedding model is loaded问题在运行RAG或相似度计算应用时遇到类似ValueError: No embedding model is loaded. Set rag_embedding_model to a valid sentence-transformers model.的错误。根因这通常发生在使用某些封装好的框架或工具如llama_index,LangChain的某些版本时没有正确初始化或指定Embedding模型。解决方案检查模型名称确保传入的模型名称字符串是sentence-transformers库支持的、存在于Hugging Face Hub上的有效模型名。例如“BAAI/bge-small-zh-v1.5”。显式初始化Embedding对象不要依赖框架的默认设置。如LangChain中应使用from langchain.embeddings import HuggingFaceEmbeddings embeddings HuggingFaceEmbeddings(model_nameBAAI/bge-base-zh-v1.5) # 然后将这个embeddings对象传递给向量数据库或检索器网络问题首次加载模型需要从Hugging Face下载确保网络通畅。也可以提前将模型下载到本地通过model_name“/path/to/your/local/model”指定本地路径。版本兼容性检查你使用的框架版本与sentence-transformers库版本的兼容性。有时升级或降级其中一个可以解决问题。Token和Embedding是大模型时代的基石技术它们将人类模糊的语言转化为机器可精确计算的空间几何关系。从理解一个句子如何被拆解成Token到这些Token如何被映射为高维空间中的点再到如何利用这些点之间的“距离”来寻找信息、生成答案——这条路径构成了当今绝大多数大模型应用的底层逻辑。我个人的体会是初期多花时间在“分块”和“Embedding模型选型”这两个环节上是性价比最高的投资。一个糟糕的分块策略会让后续所有精妙的检索和生成设计付诸东流。而选择一个与你的数据领域和任务目标匹配的Embedding模型就如同为你的知识库选择了一位理解力合格的“图书管理员”。最后一个小技巧在构建生产级RAG系统时除了关注检索的“准确性”一定要建立一套评估“答案质量”的机制。可以人工标注一批“问题-标准答案”对定期用你的系统去回答这些问题从“事实准确性”、“答案完整性”、“与上下文相关性”等多个维度进行评分。只有通过持续的评估和迭代才能让基于Token和Embedding的系统真正可靠地运转起来。