资讯动态

放弃视频课,啃透LLM底层原理:从Token到注意力机制的完整指南

发布时间:2026/10/15 23:27:03 来源:尧图企业网站定制
简介大语言模型LLM的底层实现离不开Transformer架构与Tokenization机制。理解模型如何将文本拆分为Token、通过注意力机制捕捉长距离依赖以及训练阶段如何对齐人类偏好是构建工程化应用的基础。无论是优化推理性能的KV Cache还是缓解幻觉的RAG都建立在掌握这些核心原理之上。文档作为最接近真相的一手资料能帮助开发者绕开二手转述的噪音从公式与代码层面系统建立认知框架。围绕分词、位置编码、注意力到训练对齐的完整知识链条并结合避坑经验提供一条高效啃透文档的学习路径。1. The Document is All You Need为什么我劝你放弃视频课回去啃文档很多人学 LLM 底层原理第一反应是囤几十个小时的视频课、收藏一堆二手解读文章结果三个月过去能聊术语但写不出一个推理循环。我的观点很直接一份结构完整的系统文档比任何二手转述都接近真相。标题这句话玩的是 Attention is All You Need 的梗但它真正的意思是——你不需要满世界找资料把一份扎实的入门文档从头到尾啃透token 怎么切、注意力怎么算、训练怎么对齐这些底层逻辑都能立起来。这份指南适合谁适合已经会写 Python、跑过几个模型调用但想知道模型内部到底发生了什么的人。它会回答三个问题输入文本经历了什么、参数在训练中学到了什么、推理时为什么有的回答靠谱有的在胡诌。我接下来按文档常见的知识组织顺序把每个环节的要点、可复现的代码和容易踩的坑拆开讲你跟着走一遍再回去读原文档会有完全不同的体感。2. 从 token 到输出LLM 推理的最小闭环2.1 分词与嵌入文本是怎么变成数字的文档里排在最前面的通常是分词Tokenization它是整个链条的起点。模型看的不是字符而是 token。一个 token 可能是半个词、一个词或一个常见词组分词器通过统计语料里的共现频率把高频片段合并成独立编号。中英文混合文本在分词阶段最容易让人迷惑英文一个词常被切成两三个 token中文则一个字可能对应一个 token这直接影响后续的上下文长度计算。分词之后是嵌入Embedding也就是把 token 编号映射成向量。这个映射表是模型的可学习参数之一训练初期是随机的随着训练进行语义接近的词在向量空间里会逐渐靠近。文档里通常会给出一个关键维度嵌入维度。常见的小模型用 768 或 1024大一些的到 4096 或更高。这个维度决定了每个 token 被表示成多长的向量也决定了注意力计算时的矩阵规模。用一段最小代码可以还原分词合并的直观过程。以下用类似 BPE 的合并逻辑演示只是为了理解“高频片段优先合并”这个原则分词器实际实现会更精细import re from collections import defaultdict corpus [low, lower, lowest, new, newer] vocab set(char for word in corpus for char in word) merges [] def get_stats(corpus): pairs defaultdict(int) for word in corpus: symbols word.split() for i in range(len(symbols) - 1): pairs[symbols[i], symbols[i 1]] 1 return pairs for _ in range(10): pairs get_stats(corpus) if not pairs: break best max(pairs, keypairs.get) merges.append(best) # 合并后更新语料 new_corpus [] for word in corpus: new_word word.replace( .join(best), .join(best)) new_corpus.append(new_word) corpus new_corpus print(合并顺序:, merges)这段代码每次选出现频率最高的相邻字符对合并low和er这样的组合会优先被合并成整体。参数说明corpus是训练语料列表merges存合并顺序max(pairs, keypairs.get)取频次最高的对。实际分词器还会设置vocab_size上限合并到指定规模就停止。理解这个逻辑后你就知道为什么模型对拼写错误的词处理不稳定——少见拼写会被切成更碎的 token语义信息被拆散嵌入向量能捕捉到的上下文就少了。2.2 位置编码与采样顺序信息和概率输出从哪来Transformer 本身没有顺序感如果不加位置信息把句子倒过来输入得到的向量表示是完全相同的。位置编码就是给每个 token 的嵌入向量加上位置标记。经典实现是正弦余弦函数不同的频率对应不同的位置后来的模型多用可学习的位置嵌入或旋转位置编码。文档这一节的核心要点是位置编码不是可选项它决定模型能否感知词序。模型最后一层输出的是每个 token 位置的概率分布但直接取概率最大的那个 token 会让回答变得呆板。采样策略就是在这个概率分布上做文章。温度参数控制分布的尖锐程度温度越低越接近贪心越高越随机top-p 把累计概率达到阈值的候选词圈进来再从中采样。这两个参数是你在配置文件里最常见到的东西import numpy as np def sample_with_temperature(logits, temperature1.0, top_p0.9): logits logits / max(temperature, 1e-8) probs np.exp(logits - np.max(logits)) probs probs / np.sum(probs) sorted_idx np.argsort(probs)[::-1] sorted_probs probs[sorted_idx] cum_probs np.cumsum(sorted_probs) cutoff int(np.searchsorted(cum_probs, top_p) 1) top_idx sorted_idx[:cutoff] out_probs np.zeros_like(probs) out_probs[top_idx] probs[top_idx] out_probs out_probs / np.sum(out_probs) return int(np.random.choice(len(probs), pout_probs))temperature为 0 时退化为纯贪心分母加上1e-8防止除零top_p的 cutoff 用searchsorted找累计概率的边界索引。实际框架里 Logits 是二维以上的张量上面是单步向量版本。你在文档里还会看到重复惩罚repetition penalty它是对已出现 token 的分数做折扣防止复读机。推理时采样这一步在 GPU 上是串行的因为它依赖上一步产出的 token这也是为什么长文本生成越往后越慢不是算力不够是依赖链条变长了。3. 注意力机制是骨架文档里反复出现的 Q/K/V 到底是什么3.1 从自注意力公式到最小实现文档里公式最多的地方就是注意力机制核心公式就一个softmax(Q K.T / sqrt(d_k)) V。Q 是查询向量K 是键向量V 是值向量。你可以把它们理解成检索系统Q 是你在搜索引擎里输入的问题K 是网页标题V 是网页正文。Q 和 K 做点积得到相似度除以sqrt(d_k)是为了防止点积数值过大导致 softmax 梯度消失。自己动手实现一遍单头注意力比看十遍公式都管用。下面这段代码用 numpy 复现了单头自注意力的前向计算import numpy as np def self_attention(x, w_q, w_k, w_v, maskNone): # x: [seq_len, d_model], w_q/w_k/w_v: [d_model, d_k] q x w_q # [seq_len, d_k] k x w_k v x w_v scores q k.T / np.sqrt(k.shape[-1]) # [seq_len, seq_len] if mask is not None: scores np.where(mask, scores, -1e9) weights np.exp(scores - scores.max(axis-1, keepdimsTrue)) weights weights / weights.sum(axis-1, keepdimsTrue) out weights v return out, weightsmask参数很关键训练时防止模型看到未来 token需要把未来位置的分数设成-1e9softmax 之后这些位置的概率就趋近于零。scores.max这一步是数值稳定处理防止指数运算溢出。这里d_k是 Q/K/V 的投影维度通常小于d_model目的是把注意力计算限制在低维空间降低参数量。多头注意力就是并行做多组这样的投影每组捕捉不同的依赖关系比如有的头关注语法结构有的头关注指代关系。3.2 KV Cache推理加速的关键机制文档里关于推理优化的章节一定会讲 KV Cache。生成第 N 个 token 时前面 N-1 个 token 的 K 和 V 其实已经算过了如果不缓存每次都从头重算时间复杂度是 O(N²)长文本生成会慢得无法接受。KV Cache 就是拿内存换算力把历史 K/V 存下来新 token 只算自己的 Q然后和历史 K 做点积。这个机制直接影响了显存占用。7B 级别的模型层数 32头数 32d_k是 128缓存 2048 个 token 的 K/V单条序列约占用32 × 32 × 128 × 2 × 2048 × 2字节算下来接近 1GB。所以长上下文的成本不只是模型参数KV Cache 占的显存往往更吓人。很多项目遇到的“长文本生成到一半显存溢出”不是模型本身变大了是 Cache 涨到临界点。def generate_with_kv_cache(model, input_ids, max_new_tokens, cacheNone): # cache: dict存放每一层的 k/v 张量 for _ in range(max_new_tokens): # 首次调用传全部 input_ids之后只传最后一步 if cache is None: logits, cache model(input_ids, use_cacheTrue) else: logits, cache model(input_ids[:, -1:], cachecache, use_cacheTrue) next_token logits[:, -1, :].argmax(dim-1) input_ids torch.cat([input_ids, next_token.unsqueeze(0)], dim-1) return input_ids这段代码展示了 KV Cache 的标准用法首轮传入完整序列后续每轮只喂最后一个 token配合use_cacheTrue让模型返回更新后的缓存。注意看第二次调用的输入是input_ids[:, -1:]这一步省掉了前面所有 token 的重算。你会看到很多推理框架把它称为“Prefill Decode”两段式Prefill 阶段并行处理所有输入 token 并填充 CacheDecode 阶段逐 token 生成。4. 训练与对齐预训练、指令微调与 RLHF 的分工4.1 预训练到底在学什么文档里讲训练的章节核心要抓住一个点预训练的目标是 next token prediction也就是根据前文预测下一个 token。这个目标极其简单但数据量和参数量堆上去之后模型会涌现出语法、事实、逻辑推理能力。这里有一个常见误解预训练不是让模型“记住”语料而是让模型学会一个概率分布什么 token 在什么上下文里更可能出现。预训练的数据配比是文档里容易略过但很重要的部分。一般会混合多种来源网页、书籍、论文、代码。比例不同模型的能力偏向就不同代码占比高的模型在推理任务上通常更强。数据清洗也占大头去重、过滤低质量文本、按质量打分排序这些环节对最终效果的影响不亚于模型结构本身。文档里如果给出数据配比表值得多看两眼。预训练的超参数里学习率调度是玄学重灾区。常用的是余弦衰减加 warmupwarmup 步数通常占总步数的 1% 到 5%。学习率峰值太大训练早期就会发散太小则收敛太慢。批次大小和数据总量是耦合的业界有个经验法则总 token 数约等于模型参数量乘以某个倍数比如 20 倍左右太小会欠拟合太大则浪费算力。4.2 SFT 与 RLHF从会续写到会对话预训练出来的模型只会续写文本你问它问题它大概率接着你的话往下写而不是给出答案。SFT监督微调就是把一批高质量的问答对喂进去用同样的 next token prediction 目标让模型学会“提问后应该跟回答”的交互格式。这一步的关键是数据质量而非数量几千条精心标注的对话效果往往好过几万条粗糙抓取的问答。到了 RLHF 阶段模型要学习的是“什么回答更符合人类偏好”。它分三步走先训练一个奖励模型给回答打分再用 PPO 算法微调策略模型让高奖励的回答概率变大。文档里如果讲这块通常会配一个三阶段流程图你记住它们的顺序和职责就行预训练管能力、SFT 管格式、RLHF 管偏好。# PPO 更新中的核心目标伪代码 def ppo_loss(old_log_probs, new_log_probs, advantages, clip_epsilon0.2): ratio (new_log_probs - old_log_probs).exp() unclipped ratio * advantages clipped ratio.clamp(1 - clip_epsilon, 1 clip_epsilon) * advantages return -torch.min(unclipped, clipped).mean()clip_epsilon控制策略更新的幅度防止一次更新太大导致模型崩坏。advantages是优势函数表示当前动作比平均水平好多少它由奖励模型打分和基线值函数共同计算。你在文档里看到 PPO 部分觉得吃力是正常的只需理解训练后的模型不会在所有任务上都变强对齐过程有时会牺牲部分能力一些论文把它叫“对齐税”。4.3 幻觉问题文档怎么解释代码怎么缓解幻觉是 LLM 最容易被吐槽的问题文档里通常会从两个角度解释第一模型学的是概率分布不是事实数据库它没有“查证”这个动作第二训练数据里有噪音和错误信息模型把它们也学进去了遇到相关上下文就会复述出来。还有一个冷门角度采样策略本身会放大幻觉温度调高后模型更倾向于探索低概率的 token这些 token 对应的知识往往是训练数据里的冷门片段。缓解幻觉的常规手段分推理期和训练期。推理期最简单的是降低温度、开启重复惩罚稍复杂的是引入外部检索把相关文档片段拼进上下文让模型基于检索结果回答这就是 RAG 的基本形态。训练期的办法包括用高质量数据微调、在 RLHF 阶段对幻觉回答打低分。代码层面一个轻量做法是在采样时对低置信度候选做过滤def filter_low_confidence(logits, threshold0.6): probs torch.softmax(logits, dim-1) top_prob, top_idx probs.max(dim-1) if top_prob threshold: return None # 转人工或触发兜底策略 return top_idx.item()threshold设太严会把正常回答也挡掉一般取 0.5 到 0.7 之间需要按任务调。注意这个方法只在概率能真实反映置信度时有效模型过度自信时它就失灵了所以不能依赖单一手段。5. 避坑指南LLM 原理入门最常见的 5 个翻车现场5.1 把示意图当真相二维空间想象不出高维语义现象很多人看完文档里那张“词向量空间分布图”以为语义相近的词真的在二维平面上挨着于是对模型能力产生错误预期。原因文档里的示意图是为了让读者理解“语义相似性”这个抽象概念实际嵌入空间是几百甚至几千维二维投影会严重失真。解决不要用直觉理解高维空间。想验证词向量的语义关系用余弦相似度算具体数值而不是看图。比如king和queen的相似度可能在 0.7 左右但king和cat可能是 0.2你要关注相对关系而不是绝对数值。5.2 忽略 token 数量对成本的影响现象把模型上下文长度当成免费资源一个任务塞几千字的背景资料结果账单出来吓一跳。原因API 计费按 token 数来算输入和输出都收费。更隐蔽的是你自己的本地推理也会因为 KV Cache 增长而变慢。解决养成估算 token 的习惯英文大约 4 个字符算 1 个 token中文大约 1 到 1.5 个字算 1 个。做检索增强时先对文档做切分和过滤只挑相关片段进上下文而不是整篇塞进去。5.3 注意力矩阵的可视化看多了以为模型“理解”了语法现象对着注意力热力图断言某个头专门负责主谓一致某个头负责指代消解言之凿凿。原因注意力权重高只代表计算路径上这个位置获得了更多加权不代表模型有符号化的语法知识。很多论文做过探针实验发现注意力模式与语法结构的相关性并不稳定。解决把注意力权重当成一种解释工具而不是机制本身。真要验证一个头做了什么做消融实验——把这个头的输出置零看模型表现掉多少这比盯热力图可靠得多。5.4 训练损失降不下去先怀疑模型结构再怀疑代码现象训练时 loss 卡在某个平台期不降于是一遍遍调网络层数、调注意力头数浪费时间。原因多数情况是数据问题或训练配置问题。比如学习率太大导致震荡、batch 太小导致梯度噪声大、数据里有大量重复样本导致模型过拟合到重复模式。解决先用小规模数据跑通比如 100 条样本过拟合到接近零 loss这一步能确认代码逻辑没问题再去查数据质量和超参数。如果小数据都过拟合不了大概率是代码里有 bug比如 mask 没传对、位置编码加错了地方。5.5 微调之后效果反而变差以为是自己操作不对现象用几千条业务数据做 SFT结果通用能力明显下降连基础问答都变笨了怀疑是学习率没调好。原因这是灾难性遗忘微调时模型把新任务的权重覆盖了旧知识尤其当新数据分布和预训练数据差异大时更容易发生。解决SFT 时混合一部分通用数据通常按 7:3 或 8:2 的比例混合通用数据和业务数据能有效缓解。学习率要低于预训练的学习率一般取预训练峰值的十分之一以下。另外就是加 LoRA 这类参数高效微调方法只训练低秩矩阵作为增量对原始权重的扰动小得多。6. 进阶验证法用“复现三问”检验自己是否真读懂了文档最后一个技巧是我看完文档后必做的验证动作合上资料给自己提三个问题能答上来说明读懂了答不上就回去重读对应章节。这三问分别是这个机制解决什么问题、输入输出是什么、如果去掉它会怎样。以注意力机制为例它解决的是长距离依赖问题让模型能直接建立任意两个位置的关系输入是 Q/K/V 三个向量序列输出是加权求和后的新向量序列去掉它模型就只能靠相邻位置传递信息长文本理解能力会断崖式下降。这个“复现三问”的精髓在于逼你用自己的话重构概念而不是背公式。更硬核的验证方式是动手改代码。比如文档里给了自注意力实现你可以试着把sqrt(d_k)去掉跑到深层网络里观察 loss 是否更容易爆掉这比读十遍“防止梯度消失”的论述都有说服力。或者把 KV Cache 关掉对比生成同样长度文本的耗时差异你会直观感受到 O(N²) 和 O(N) 的差距。我的习惯是每读完一个主题章节就写一个几十行的最小复现代码不追求性能只追求逻辑链条完整。这个习惯帮我发现过不少“以为自己懂了一写代码就卡壳”的知识盲区。比如位置编码我原以为就是加一个正弦波向量真动手才发现维度匹配、频率归一化、和嵌入向量怎么相加每一步都有细节。如果你现在刚拿到手这份指南我建议你换个读法第一遍从头到尾快速过标注不懂的名词第二遍抓住第 2 到第 4 章的核心实现代码对照公式逐个敲一遍第三遍回到开头重点看之前标注的地方。三遍下来底层原理的框架就立住了。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑