[{"data":1,"prerenderedAt":328},["ShallowReactive",2],{"article:column:/column/llm-to-agent-learning/20":3},{"id":4,"title":5,"body":6,"description":320,"extension":321,"meta":322,"navigation":323,"path":324,"seo":325,"stem":326,"__hash__":327},"column/column/llm-to-agent-learning/20.md","20｜上下文为什么贵：prefill、decode与KV缓存",{"type":7,"value":8,"toc":303},"minimark",[9,13,16,21,24,27,30,33,36,40,43,46,49,52,59,62,65,68,71,74,77,87,91,94,97,100,103,109,112,115,119,122,128,131,134,137,140,148,154,157,160,164,167,170,173,176,179,182,186,189,192,200,203,206,209,212,215,219,224,227,232,235,240,243,246,251,254,259,262,267,270,273,278,281,286,289,294,297,300],[10,11,12],"p",{},"把一页通知交给模型，它很快开始回答；换成一本书，第一句话迟迟没有出现。另一种情况更奇怪：开头等得不久，后面却像挤牙膏一样，一小段一小段冒出来。这两种慢发生在同一条生成链上，却未必来自同一个瓶颈。",[10,14,15],{},"第19章已经说明，回答通常按token逐步产生。本章把镜头移到计算与存储：哪些工作必须重新做，哪些结果可以留下，以及“支持很长的输入”为什么不能直接读成“能用好全部输入”。我们采用最常见的因果、decoder-only Transformer作解释，不把这套缓存公式当成所有模型的统一结构。",[17,18,20],"h2",{"id":19},"_1先读输入再逐步续写","1．先读输入，再逐步续写",[10,22,23],{},"假设输入已经切成四个token，记作A、B、C、D。prefill常译为预填充：模型处理这些已知位置，在每一层形成表示和注意力所需的K、V，并用最后位置的输出预测第一个新token。",[10,25,26],{},"四个输入都已知，因此在同一层内可以成批计算。因果掩码仍然有效：A不能读取B，C不能读取D。这里的并行是把许多已知位置安排在一起计算，并没有让某个位置偷看未来。不同层之间仍有依赖，后一层要用前一层的结果。",[10,28,29],{},"假设首次预测选中了E。下一次计算才把E作为新的输入位置，获得它的表示，再预测F。这个逐步接收新token、预测下一个token的阶段叫decode。词语容易让人误会：这里的decode不是把一份现成的完整答案翻译成人话，而是生成流程中的逐步计算。",[10,31,32],{},"因此，prefill已经负责产生第一个新token的预测分布；不能在时间图上凭空多算一轮“先只读完，再从零预测E”。实际服务还可能有排队、分词、网络传输和调度，这些都不属于上述纯模型计算，但用户感受到的等待包含它们。",[10,34,35],{},"通常分别观察两个指标：首token延迟是请求到第一个输出可见的时间；输出阶段的token间延迟描述后续生成节奏。总耗时还依赖输出有多长。只给“每秒生成多少token”，可能掩盖长输入带来的开头等待；只给首token时间，也可能掩盖后面的慢速生成。",[17,37,39],{"id":38},"_2为什么过去的kv能够留下","2．为什么过去的K、V能够留下",[10,41,42],{},"第8章的注意力把当前查询Q与各位置的K匹配，再按份额组合V。对因果模型来说，旧位置只依赖它自己和它之前的内容。只要前缀、参数及相关计算设置不变，新token接在后面，不会回头改变旧位置已经得到的表示。",[10,44,45],{},"这可以逐层检查，而不必靠“过去已经发生”来猜。固定前缀A、B、C、D的token和位置编号，固定模型参数，关闭训练时随机扰动，并保持相同掩码与位置编码规则。最底层A到D的输入没有变化；第一层每个旧位置仍只读取它原来的前缀，输出就没有新的依赖。第二层收到的旧位置输入也相同，以此逐层推下去，旧K、V便保持不变。追加E只增加一个允许读取A到E的新位置，不会在旧位置行里增加对E的读取。",[10,47,48],{},"这里的“不变”是相同数学计算的结果。不同内核和精度可能造成很小的浮点差异；如果参数、位置计算、掩码或前缀本身改变，就必须重新判断缓存条件。依赖完整输入的双向编码器也不满足这个因果论证。",[10,50,51],{},"先看图1的下三角。每个有色格表示“这一行的查询允许读取这一列的键”，不是存放一个token。追加E增添一行；把D改成D′则改动了已有输入，失效边界从D开始。",[10,53,54],{},[55,56],"img",{"alt":57,"src":58},"20｜上下文为什么贵：prefill、decode与KV缓存：图1","/collections-assets/llm-to-agent-learning/a3ed5049ae5f160bfa6af708e60ce5f7457a95f74aa77b3b0f87ace617a63351.png",[10,60,61],{},"图1｜固定参数与位置规则的因果注意力中，追加E不会给A到D增添依赖，因此旧计算可复用。若把D改成D′，以共同前缀A到C为安全复用边界，D及后续位置统一重算。",[10,63,64],{},"于是，在每一层保存旧位置的K、V，下一步就可以复用。这份按层组织的中间结果叫KV缓存。它存的是计算出来的向量，不是原文的精简摘要，也不是新增的模型权重。不同会话即使使用同一模型，输入不同，缓存通常也不同。",[10,66,67],{},"为什么不把旧Q也照样长期存下？逐步续写时，当前这一步主要需要新位置的查询，让它读取旧的键和值。旧位置的查询曾用于计算旧位置的输出，通常无须重新发起相同读取。缓存不是把Q、K、V三个字母中随意挑两个，而是顺着计算依赖留下未来要用的对象。",[10,69,70],{},"把问题具体化：E进入模型时，每一层只为E算新K、V；E的Q读取A到E的K，并汇总对应V。下一层要等本层对E的处理完成，才能形成自己的新缓存项。因此不能先在最底层计算一个E的向量，然后把它复制到所有层当成缓存。",[10,72,73],{},"再把“读取”写成张量形状。只看一条序列的一层、一个注意力头，设每头向量宽度为d。E进入前缓存的K、V各有4行、d列；新算的K_E、V_E各有1行、d列，追加后变成5行、d列。Q_E是1行、d列，与5个K点积得到1行、5列分数，归一化后得到5个读取权重，再加权组合5行V，得到1行、d列输出。",[10,75,76],{},"被省掉的是重新生成旧位置K/V及相关旧计算；新Q与旧K的匹配、新权重对旧V的组合仍要执行。因为Q_E一般不同于Q_D，不能直接沿用上一位置的注意力权重。这也是缓存会省计算、却仍要持续读取长前缀的原因。",[10,78,79,80],{},"Hugging Face的Transformers缓存说明明确区分逐层保存的旧K/V和每一步新算的部分，展示了把新键值接到旧缓存后的注意力计算。这里用它核实实际框架里的对象含义，不把文档中的简化复杂度说明当成整台机器的速度预测。",[81,82,86],"a",{"href":83,"rel":84},"https://huggingface.co/docs/transformers/main/en/cache_explanation",[85],"nofollow","Transformers缓存机制",[17,88,90],{"id":89},"_3在纸上走完三次预测","3．在纸上走完三次预测",[10,92,93],{},"拿出三行纸。第一行写“处理A、B、C、D，预测E”；第二行写“处理E，预测F”；第三行写“处理F，预测G”。每行再画两栏，分别记录本轮新算的K/V与计算结束后的缓存。",[10,95,96],{},"第一行新算四组，缓存为A到D。第二行新算E的一组，缓存变成A到E。第三行新算F的一组，缓存变成A到F。G刚刚被选出来，还没有作为下一轮输入经过各层，所以此刻缓存没有G。这个相差一位的细节，可以检查我们是否真的理解“当前位置预测下一位置”。",[10,98,99],{},"若每次把整个前缀重新送入、不复用旧结果，这三次预测分别处理4、5、6个位置，共15个位置；复用缓存只处理4＋1＋1＝6个新位置。每层的K与V投影分别对应15次和6次位置级工作。我们只是在数新旧位置，不是在报告速度快了2.5倍。",[10,101,102],{},"原因是缓存方案仍要做新查询对已有前缀的读取。只数每个查询可见多少位置，首次因果prefill有1＋2＋3＋4＝10对；第二轮有5对；第三轮有6对，共21对。完全重算前缀则是10＋15＋21＝46对。两种计数衡量不同工作，不能混用成一个万能加速比。",[10,104,105],{},[55,106],{"alt":107,"src":108},"20｜上下文为什么贵：prefill、decode与KV缓存：图2","/collections-assets/llm-to-agent-learning/9fa27d0e0b4a94bafa7bf71ec7fd27c0668b85c70764737c6056e0b712354076.png",[10,110,111],{},"图2｜prefill处理A到D后预测E；两轮decode依次处理E、F，预测F、G。第二轮的张量路径显示新Q仍读取全部已缓存K/V。G被选出时尚未作为输入处理；位置数15对6与匹配对46对21是不同口径的教学计数。",[10,113,114],{},"再把D改成D′，问能不能保留原来A到D的全部缓存？不能。A到C保持相同前缀时可能仍可复用，但D及其后的结果依赖已经变化。若在最前面插入一句话，位置和前缀条件都可能改变，不能仅凭后半段文字看起来一样就拼接旧缓存。所谓前缀缓存，重用条件的核心是相同计算前缀，而不是“意思差不多”。从D开始统一重算，是按共同前缀划定的安全策略，不表示每层每个后续张量都必然改变；例如固定E嵌入生成的首层K/V可能仍相同，而高层表示会受到D变化的影响。",[17,116,118],{"id":117},"_4缓存省计算却增加一笔存储","4．缓存省计算，却增加一笔存储",[10,120,121],{},"先算普通注意力实现的简化容量。假设每层都保存完整上下文，两份缓存采用相同长度和数值精度：",[10,123,124],{},[125,126,127],"strong",{},"缓存字节数＝2×层数×并行序列数×已缓存token数×KV头数×每头维度×每个数的字节数",[10,129,130],{},"开头的2分别对应K和V。层数表示多少层需要独立保存；并行序列数表示同时维持多少份不同序列；KV头数是键值头的数量，未必等于查询头数。每头维度描述每个头的一条向量有多少个数；最后一项把数的个数换成字节。",[10,132,133],{},"构造一个便于复核的例子：2层、1条序列、6个已缓存token、2个KV头、每头4维、每个数2字节。容量为2×2×1×6×2×4×2＝384字节。长度翻倍到12，容量变成768字节；再同时维持两条这样的序列，就变成1536字节。",[10,135,136],{},"这些数故意很小，只用来跟踪乘法。真实模型还存在不同层结构、分块分配、元数据、对齐与暂存空间，不能把这个乘积直接叫作进程显存。模型权重是另一笔账，即使输入还没有一个token，也需要先安排权重的位置。",[10,138,139],{},"还可以反过来由预算推容量。沿用上述教学结构，每增加一个缓存位置需要384÷6＝64字节。假设总预算2048字节，事先为权重与运行空间预留768字节，便只剩1280字节给KV，最多容纳20个位置。这里按各序列独立保存、暂不计分配开销；20是所有序列长度之和的上限，不是每条都能用20。两条各12位置需要1536字节，已经超出KV额度256字节。输入能放下还不够，后续生成新增的位置也会继续占用预算。",[10,141,142,143],{},"Grouped Query Attention简称GQA，让多个查询头共享一组键值头。它能减少KV头数这一因子，但没有让模型只剩同样少的查询。Qwen3公开报告的架构表就分别列出Q头和KV头；这给了我们一个真实阅读方法：估算缓存时要找对列，不能拿查询头数量代替。",[81,144,147],{"href":145,"rel":146},"https://arxiv.org/html/2505.09388v1#S2",[85],"Qwen3技术报告，架构",[10,149,150],{},[55,151],{"alt":152,"src":153},"20｜上下文为什么贵：prefill、decode与KV缓存：图3","/collections-assets/llm-to-agent-learning/9e17541fecf02e459b2a144b0796239a41e366106e8104541cfc147977231021.png",[10,155,156],{},"图3｜每个位置与KV头对应4个小格，每个小格表示一个数、占2字节；一层K共有6×2×4个数，共96字节。复制为K、V两份，再为2层各存一份，得到384字节。权重与运行空间另计。",[10,158,159],{},"量化缓存可以减少每个数的字节数，但可能引入误差与额外转换；把缓存搬到主机内存能减轻设备压力，却增加传输；滑动窗口只保留近期部分，可限制增长，但改变可直接读取的信息范围。它们都要按模型和实现评估，不能理解成免费省空间。缓存也不等同于第29章的长期记忆，清掉缓存不代表系统删除了保存的原始对话记录。",[17,161,163],{"id":162},"_5长输入和长输出贵法不同","5．长输入和长输出，贵法不同",[10,165,166],{},"设输入长度为N。在普通全注意力prefill里，可见匹配对大约随N的平方增长，因为许多位置都要读取各自的前缀；因果下三角只是把完整方阵减少到约一半。某些高效注意力实现能避免把整个注意力矩阵存下来，却不因此取消全部匹配计算。",[10,168,169],{},"在带缓存的单步decode里，只有一个新查询位置，但它仍要读取已有前缀。前缀越长，需要处理的K/V通常越多，单步注意力工作随长度增长。连续生成M个新token时，读取范围又逐步扩大，不能把整段输出看成只做一次单步工作。",[10,171,172],{},"这解释了常见但并非普遍的系统现象：prefill可利用较大的批量矩阵计算；decode除了计算，还可能花大量时间把权重与缓存从内存搬到计算单元。哪一部分占主导，取决于硬件、并发、模型结构与实现。把“输入贵、输出慢”背成定律，会遗漏这些条件。",[10,174,175],{},"多个请求一起服务时，还有资源竞争。增加批处理可能提高总吞吐，但某个用户可能等得更久；保留很多会话的长缓存，也可能挤压同时服务的人数。吞吐量回答一台机器总体处理多少工作，延迟回答一个请求等多久，两者要同时看。",[10,177,178],{},"还有一个常见混淆：服务商说某段提示命中了缓存，并不一定表示用户能直接访问那份KV向量。产品可能按前缀复用、会话状态或其他实现提供缓存能力，具体保存多久、何时命中、如何计费都属于服务规则。仅从接口返回一个标志，不能反推出底层每层怎样分配内存。",[10,180,181],{},"如果要比较两个方案，至少固定同一模型、输入长度、输出长度、并发数和精度，再分别记录首token时间与后续时间。把一次短问答与另一条长作文放在一起，就算计时毫无误差，也测不出缓存本身的收益。缓存开关改变了计算复用；内容与负载变化则同时改变了任务，因果结论需要把它们分开。",[17,183,185],{"id":184},"_6装得下不等于用得好","6．装得下，不等于用得好",[10,187,188],{},"上下文窗口规定输入、输出及某些内部token需要满足的容量限制，具体计数以模型说明为准。容量是接口和实现条件；它没有保证散落在长材料中的每条证据都能被检索，更没有保证模型会正确处理跨段关系与冲突。",[10,190,191],{},"可以给同一份材料做三个逐级加难的纸笔测试：先问某个明确编号对应哪个名字，再问相隔多页的两条规定谁覆盖谁，最后问一个需要同时满足日期、人员和例外条件的问题。第一题只需要定位，后两题还要整合证据和推理，不能用第一题的成功替代全部能力。",[10,193,194,195],{},"《Lost in the Middle》研究观察到，在其测试模型与任务中，相关信息所处位置会影响表现，中间位置可能更难利用。这是特定实验条件下的证据，不意味着每个后来的模型都有相同曲线；它提示评测应改变证据位置、干扰材料和所需关系，而不只把窗口数字写大。",[81,196,199],{"href":197,"rel":198},"https://arxiv.org/abs/2307.03172",[85],"长上下文利用研究",[10,201,202],{},"一个实用取舍是：任务只需几条有出处的规定时，先检索再给上下文，可能比塞入整本资料更清楚；需要比较全文结构或找未知关联时，过早删减又可能丢掉关键证据。长上下文和检索并不互相替代，问题在于怎样保留完成任务需要的信息。",[10,204,205],{},"这与软件工程的缓存和增量计算共享一个约束：复用前，要知道结果依赖哪些输入与计算规则。KV的依赖不只是最后一段文字，还包括共同前缀的token与位置、模型参数，以及掩码等设置。只给末尾同一句话取相同名字，不能抹掉它前面不同的上下文；换过参数后，也不能继续假定旧KV有效。这里复用的是满足条件的数值计算，不是把“意思相近”当作等价。",[10,207,208],{},"一个小型等价性测试可以固定同一前缀与同一模型，比较“整段重算”和“按前缀缓存续算”的下一token分数，在预先规定的数值容差内核对；再修改D，检查从D开始重算是否恢复一致。这个测试检查缓存有没有改变结果。另一组固定输入、输出长度与负载的计时，才检查它是否更快。前面的容量账还要单独检查是否装得下：正确性、容量和性能各有自己的证据。",[10,210,211],{},"缓存是否保留还与服务生命周期有关。一次生成结束后，系统可能释放这份中间结果，也可能为后续相同前缀暂存。再次发起对话能看到旧文字，不代表上一轮的缓存一定还在；系统也可能重新读取历史，再算一遍。这是数据持久化与计算复用的又一个区别。",[17,213,214],{"id":214},"本章练习",[216,217,218],"h3",{"id":218},"题目",[10,220,221],{},[125,222,223],{},"问题1｜沿时间与依赖检查缓存",[10,225,226],{},"模型刚处理F并选出G时，缓存包含到哪个位置？接着把D改成D′、A到C不变，哪些旧位置可沿原有因果计算复用，哪些必须重算？说明原因。",[10,228,229],{},[125,230,231],{},"问题2｜把变动的乘数圈出来",[10,233,234],{},"沿用图3的2层、1序列、6token、2个KV头、4维、每数2字节。把缓存长度改为12、并行序列数改为2，其他项不变。缓存多少字节，变为几倍？整体显存是否必然同倍数变化？若总预算2048字节、为权重与运行空间预留768字节，这份缓存能否放下？仍忽略分配开销。",[10,236,237],{},[125,238,239],{},"问题3｜构造长上下文测试",[10,241,242],{},"同一份资料中，某条规则在开头，一条例外在中间，一条修订在末尾。请各设计一个只检查定位、检查跨段关系的问题，并说明只答对定位题能支持什么结论。",[216,244,245],{"id":245},"思考提示",[10,247,248],{},[125,249,250],{},"提示1｜对应问题1",[10,252,253],{},"区分“选出的下一个token”和“已经送入各层的当前位置”；沿图1查看一个旧位置会依赖哪些输入。",[10,255,256],{},[125,257,258],{},"提示2｜对应问题2",[10,260,261],{},"只改长度与序列数两个乘数。区分KV这笔账与整个进程的内存账，再从总预算减去预留空间。",[10,263,264],{},[125,265,266],{},"提示3｜对应问题3",[10,268,269],{},"定位题只需返回一处资料；关系题应迫使答题者同时使用至少两处，并给出适用条件。",[216,271,272],{"id":272},"参考答案与解析",[10,274,275],{},[125,276,277],{},"答案1｜对应问题1",[10,279,280],{},"缓存到F。G目前只是被选中的输出，要在下一轮成为输入才会形成自己的逐层K/V。改D后，A到C在前缀、位置及参数均相同的条件下可复用；D到F依赖改变，应重算。不能因E、F的token字面相同就保留其高层缓存，因为它们会读取已改变的前缀表示。",[10,282,283],{},[125,284,285],{},"答案2｜对应问题2",[10,287,288],{},"2×2×2×12×2×4×2＝1536字节，是384字节的4倍。两个翻倍分别来自长度与并行序列数。整体显存还包括不随这两个乘数同样变化的模型权重、临时对象等，不能直接说也变为4倍。预算中KV可用2048−768＝1280字节，1536超出256字节，因此在给定条件下放不下。",[10,290,291],{},[125,292,293],{},"答案3｜对应问题3",[10,295,296],{},"一种定位题是“中间那条例外规定了哪个日期？”一种关系题是“对修订生效后的该类人员，原规则还是例外适用？请结合三处证据解释。”其他能分别隔离定位与关系整合的题目也可接受。定位成功支持模型在这次位置、材料与提问条件下找到了信息；它不单独证明能处理覆盖、冲突和例外，还应改变证据位置和干扰内容复测。",[17,298,299],{"id":299},"下一章",[10,301,302],{},"现在知道了每多生成一些token，就多做一些计算、多保留一些状态。那么，故意给模型更多时间，让它多试几条解法、再检查结果，究竟怎样换来更可靠的答案？第21章把“多想一点”拆成可计数的推理时计算。",{"title":304,"searchDepth":305,"depth":305,"links":306},"",3,[307,309,310,311,312,313,314,319],{"id":19,"depth":308,"text":20},2,{"id":38,"depth":308,"text":39},{"id":89,"depth":308,"text":90},{"id":117,"depth":308,"text":118},{"id":162,"depth":308,"text":163},{"id":184,"depth":308,"text":185},{"id":214,"depth":308,"text":214,"children":315},[316,317,318],{"id":218,"depth":305,"text":218},{"id":245,"depth":305,"text":245},{"id":272,"depth":305,"text":272},{"id":299,"depth":308,"text":299},"从信息表示和学习出发，用可核算的例子与白板图逐步理解大模型、后训练、推理和Agent系统。","md",{},true,"/column/llm-to-agent-learning/20",{"title":5,"description":320},"column/llm-to-agent-learning/20","kR6hIFjlJhLg4iTd-FZPUMdU33INoA1HA3fNCOIDXbc",1791935828763]