Speculative Decoding 为什么能加速:从并行验证到 GPU Memory-Bound

副标题 / 摘要 Speculative decoding 并不是让大模型先生成一遍,再与小模型逐个比较;它让小模型先提供未来 token 的候选路径,使大模型能在一次 forward 中并行验证多个位置。它的本质,是用更多、甚至部分无效的并行计算,换取更少的串行 Target forward。 预计阅读时长:12~16 分钟 目标读者:了解 Transformer 基础,希望理解 LLM 推理性能、vLLM 或 speculative decoding 的读者 核心问题:为什么一次验证多个 token 会比逐个生成更快?如果中间猜错,后面的计算是否全浪费了? 先给结论 理解 speculative decoding,只需要抓住四件事: 自回归生成慢在串行依赖:不知道前一个 token,就无法确定下一个 token 的条件分布。 Draft 模型先猜出一条候选路径后,Target 模型就能在一次 forward 中并行计算这条路径上多个位置的条件分布。 第一个未被接受的 token 之后,验证结果确实全部作废;这是用并行冗余换串行轮次的代价。 加速是否成立,取决于接受长度、Draft 成本、Target verify 成本、batch 大小和 KV Cache 流量,而不是只看一次 draft 了多少个 token。 一句话概括: Draft 模型最重要的作用,不只是“帮大模型猜答案”,而是暂时补上未知的未来输入,让 Target 能跨过原本必须串行执行的多个解码位置。 普通自回归生成为什么必须串行 假设当前上下文是: I love Target 模型准备生成: New York City very much 普通 decode 只能这样执行: ...

2026年8月26日 · 6 分钟 · map[name:Jeanphilo]

Attention-Based Seq2Seq:为什么会自然过渡到 Transformer

副标题 / 摘要 这篇文章专门解释一个关键过渡:为什么 fixed-length 的 seq2seq 很快会不够用,attention-based seq2seq 是怎么补上“按需读取源序列”这个能力的,以及这个思路为什么几乎会自然长成 Transformer。最后会收束到一份最小可运行的 PyTorch GRU + additive attention 实现。 从“苹果”为什么老翻不准开始 还是用这个最小翻译任务: src: <bos> I really love green apples <eos> tgt: <bos> 我 真的 喜欢 青 苹果 <eos> 当 decoder 走到要生成“苹果”这一步时,最理想的行为其实很明确: 它应该重点回头看源序列里的 apples 它可能顺手也看一眼 green 它不能只依赖一个已经被反复压缩过很多轮的最终隐藏状态 如果你用上一篇里那个最小 seq2seq: encoder 把整句读完 只把最后一个 hidden_enc 交给 decoder decoder 后面每一步都只靠这个固定长度状态和自己的历史 那么句子一长,这里就会出现一个很具体的问题: decoder 明明需要“现在按需去看源序列的某几个位置”,但 fixed-length seq2seq 只给了它“一次性打包好的整句摘要”。 这就是 attention-based seq2seq 出现的真实压力。 它不是为了“概念更高级”,而是因为 decoder 在每个时间步都需要重新决定自己该看源序列的哪里。 快速掌握地图 fixed-length seq2seq:encoder outputs -> 丢弃大部分,只保留 final hidden attention-based seq2seq:decoder step t -> 对所有 encoder outputs 打分 -> 加权求和得到 context_t 核心收益:不同目标位置可以读取不同源位置 仍然存在的限制:encoder 和 decoder 还是循环结构,时间上依然串行 通向 Transformer 的关键桥:decoder state 作为 query,encoder outputs 作为 memory 这篇文章重点深挖的两个概念 对齐分数与上下文向量:decoder 怎样在每一步决定“该看源序列哪里” 从 attention-based seq2seq 到 Transformer 的结构映射:哪些东西被保留了,哪些东西被替换了 大师级心智模型 fixed-length seq2seq 的核心假设是: ...

2026年4月23日 · 9 分钟 · map[name:Jeanphilo]

Seq2Seq 与 Encoder-Decoder:从翻译任务到最小可运行 PyTorch 实现

副标题 / 摘要 这篇文章不把 seq2seq 和 encoder-decoder 当成术语表来讲,而是从一个最小翻译任务出发,解释为什么“输入一段序列、输出另一段序列”会自然逼出编码器和解码器的分工,最后收束成一份最小可运行的 PyTorch GRU 实现。 从一个最小翻译任务开始 假设源序列是: <bos> I love apples <eos> 目标序列是: <bos> 我 喜欢 苹果 <eos> 当模型要生成“苹果”时,它至少要解决三件事: 它必须知道整句英文大意,而不只是当前一个词 它必须记住自己前面已经生成了“我 喜欢” 它必须按顺序一个词一个词地产生输出,而不是一次吐出整个目标序列 如果你只用一个普通分类器把源句子映射成一个类别,这个任务做不成。 因为这里的输出不是一个固定标签,而是长度可变的目标序列。 所以这里天然会逼出一个更具体的数据流: 先把源序列读完,压成可传递的状态 再从 <bos> 开始,逐步生成目标序列 每生成一步,都要同时依赖“源侧信息”和“目标侧历史” 这就是 sequence-to-sequence 的最小问题形态。 seq2seq 说的是任务:输入一段序列,输出另一段序列。 encoder-decoder 说的是实现:先编码输入,再逐步解码输出。 下面不先堆名词,直接按这个压力把代码一步一步长出来。 快速掌握地图 问题形态:src -> encoder -> hidden/context -> decoder -> logits 核心目标:学习条件概率 p(y_t | y_{<t}, x) 最小实现:Embedding + GRU Encoder + GRU Decoder + output projection 何时适用:翻译、摘要、改写、问答这类“输入序列 -> 输出序列”任务 明显局限:如果所有源信息都被压进一个固定长度向量,长句子会吃力 这篇文章重点深挖的两个概念 隐藏状态交接:encoder 到底把什么交给 decoder 右移目标序列与 teacher forcing:训练时 decoder 为什么不能直接喂完整真实答案 大师级心智模型 这类模型的核心抽象不是“两个 RNN 拼起来”,而是: ...

2026年4月23日 · 7 分钟 · map[name:Jeanphilo]

Transformer 结构推导:一步一步搭出最小可运行 PyTorch 实现

副标题 / 摘要 这篇文章不把 Transformer 当成一个现成黑盒来介绍,而是直接从一个最小翻译任务开始,让需要的结构一层一层长出来,最后收束成一份最小可运行的 PyTorch encoder-decoder Transformer。 从一个最小翻译任务开始 假设源序列是: <bos> I love apples <eos> 目标序列是: <bos> 我 喜欢 苹果 当模型要生成“苹果”时: 它不能看目标序列里未来还没生成的位置 它需要重点读取源序列中的 apples 它可能还需要参考前面的“我 喜欢”来决定当前词 所以这里天然会逼出三件事: 目标侧必须有因果约束 源侧和目标侧都需要全局读取 解码器不仅要读自己,还要读编码器输出 RNN 和 CNN 也能处理序列,但它们在长距离依赖和全并行训练上都有明显限制。 所以这里真正要解决的,不只是“做一个更深的网络”,而是让任意位置能直接交互,并且显式控制信息流方向。 下面开始按这个压力一步一步长代码。 Step 1:先有输入表示,但先不谈注意力 先看一个已经分词并编号后的最小输入: <bos> I love apples <eos> -> [1, 15, 982, 204, 2] 这里的 1, 15, 982, 204, 2 还不是模型已经理解后的表示。 它们只是词表里的编号,作用更接近“标签”或“学号”: 15 比 204 小,不代表它们语义上更接近 直接拿这种离散编号去做线性变换或点积,含义也不对 所以第一步只解决一个更具体的问题: 怎样把“词表编号”变成“模型后面可以继续计算的一组连续数值”? 这里再引入两个词: token id:分词后查词表得到的离散编号 embedding:把每个离散编号映射成长度为 d_model 的可学习向量 先写最小版,不额外引入别的机制: ...

2026年4月23日 · 10 分钟 · map[name:Jeanphilo]

Attention Is All You Need:Transformer 的核心算法与工程落地

系统解释 Attention Is All You Need 的核心算法:自注意力、多头、位置编码与编码器-解码器结构,给出可运行示例与工程取舍。

2026年1月25日 · 8 分钟 · map[name:Jeanphilo]

FlashAttention 的 MQA/GQA:共享 KV 的等价、收益与实现要点(含可运行验证)

解释 FlashAttention 在 MQA/GQA 下如何利用共享 KV:从数学等价(复制 KV)到工程收益(KV cache 与带宽),并给出可运行代码验证。

2026年1月25日 · 10 分钟 · map[name:Jeanphilo]

FlashAttention 为什么能 one-pass:在线 softmax(m/l)与 Tiling 的核心思想

从标准注意力的显存 IO 账本出发,解释 FlashAttention 的核心:在线 softmax 维护 (m,l) 并流式累积输出,再配合 tiling 把数据驻留在片上存储,从而避免显式存储 $QK^\top$ 与 softmax 概率矩阵。本文给出可运行的 Numpy 分块注意力实现与数值等价验证,并用可复制的字节算账方法说明它为什么会快。

2026年1月25日 · 10 分钟 · map[name:Jeanphilo]

Softmax 工程实现与 GPU 访存优化:在线更新、融合与带宽算账(含可运行验证)

从标准两遍 softmax 的访存模式出发,推导在线 softmax(m,l)更新与正确性;进一步解释在 attention/cross-entropy 中如何通过融合避免落地概率矩阵,并用可运行代码验证等价与估算带宽收益。

2026年1月25日 · 11 分钟 · map[name:Jeanphilo]

Self-Attention 计算公式与 Softmax 数值稳定:从推导到工程实现

副标题 / 摘要 Self-Attention 的公式很短,但工程细节很长:从 Q/K/V 计算到 softmax 数值稳定、mask 与缩放,每一步都影响效果与性能。本文用 ACERS 结构给出推导、实践步骤与可运行示例。 预计阅读时长:12~16 分钟 标签:attention、transformer、softmax SEO 关键词:Self-Attention, Softmax, Scaled Dot-Product, 数值稳定 元描述:Self-Attention 的计算公式与 softmax 稳定实现方法,含工程实践与示例代码。 目标读者 想真正理解 Self-Attention 公式含义的学习者 需要处理训练不稳定/溢出的工程实践者 关注注意力数值稳定与实现细节的开发者 背景 / 动机 在 Transformer 中,Self-Attention 是计算量最大、数值最敏感的模块之一。 很多训练不稳定、输出 NaN 的问题,都来自 softmax 的溢出/下溢或 mask 的错误处理。 理解公式与稳定实现,可以显著减少工程“踩坑”。 核心概念 Q/K/V:查询、键和值,来自输入线性投影 缩放点积注意力:$\text{softmax}(QK^\top/\sqrt{d_k})V$ 数值稳定:通过减去行最大值避免 softmax 溢出 思路推导(从朴素到稳定实现) 朴素做法 先算所有相似度 $S = QK^\top$,再做 softmax 得到权重 $P$,最后 $O = PV$。 这个实现最直观,但当 $S$ 很大时会出现 exp 溢出。 关键观察 softmax 对每行同时加上或减去一个常数不改变输出: $\text{softmax}(x) = \text{softmax}(x - \max(x))$。 ...

2026年1月25日 · 3 分钟 · map[name:Jeanphilo]

单阶段 vs 双阶段目标检测:从候选集合到 NMS 的工程算账

从工程视角系统对比 one-stage 与 two-stage 检测:把它们统一成‘生成候选→打分→去重’的流程,然后用可复制的数字(anchors 数量、top-k、NMS 最坏复杂度)解释速度差异,并用 focal loss vs 采样策略解释训练差异。文末提供纯 NumPy 可运行的 NMS 与候选规模算账代码,帮助你做选型与排查。

2026年1月24日 · 8 分钟 · map[name:Jeanphilo]