为什么 AI 的第一个字出得最慢?为什么长对话越聊越贵?vLLM 凭什么把吞吐量翻了几倍?三个问题其实是同一个问题。续前篇《读懂 Attention》,这一次,走进一间深夜的病案室。我的记忆,先生,好比一个垃圾倾倒场。——博尔赫斯,《博闻强记的富内斯》,1942开篇 · 记忆是要付房租的KV Cache 是什么,为什么重要上一篇《读懂 Attention》讲的是大语言模型如何思考:一句话里的每个新词,翻检前文所有词的 k 与 v,按权重加权混合,更新对自己的理解。这一篇讲一个更朴素、却直接决定成本的问题:**那些被反复翻检的 k 和 v,存放在哪里?**存放它们的机制,叫KV Cache(键值缓存);而 2023 年一项管理它的发明——PagedAttention——直接催生了如今最流行的开源推理引擎 vLLM,把同一块显卡能同时服务的用户数翻了几倍。先给三个你多半亲历过的现象。为什么给 AI 发一段长长的提示词之后,第一个字迟迟不出,之后却逐字连珠?为什么各家大模型 API 都按上下文长度计价,对话越长越贵?为什么同一块显卡,换一个推理引擎,吞吐量就能翻倍?三个问题的答案是同一个:模型的记忆,是要付房租的,而 KV Cache 就是那间房。博尔赫斯写过一个记住一切的人,富内斯——最终被自己的记忆压垮;大模型的工程师们,每天都在跟同一个问题搏斗。读过前篇的读者,应该还记得那套语言:一段对话是一句话,每个词是一位按序出场的人;每个人有三张脸——需求 q,是他在寻求什么;可感知面 k,是别人眼中的他;给予 v,是他实际留下的东西。每位新出场者,拿自己的 q 翻检所有前人的 k,按分数掺入他们的 v——这就是注意力。没读过也不要紧,记住这三张脸,就够用了。前篇问的是:你由谁构成, 这一篇要问的是:你把他们记在哪里, 要回答它,故事得往下讲。前篇的结尾,小雨在凌晨一点接住了朋友的崩溃;几年过去,当年的深夜树洞穿上了白大褂,成了一名医生。她所在的这家医院,就是一块显卡:全体医生共同受过的医学训练,是模型的权重——前篇里那套人人共享的关系语法 W;每一位刚接班的值班医生,是一次新的计算;每位病人的一段病程,是一段对话;而存放所有病历的病案室,就是显存。今晚值班的,恰好是小雨。接下来的一切,都发生在这间病案室里。第壹章 · 铁打的病历,流水的医生为什么必须缓存老周是这一科的老病人,随诊三年,每月复查。深夜十一点,他的床头铃响了。赶到床边的是今晚的值班医生——碰巧是小雨;换一个晚上,可能是任何一位同事。医院靠轮班运转,而轮班有一条铁律:白班知道的一切,夜班未必知道;夜班能依靠的,只有一样东西——病历。这恰好是大语言模型最反直觉、也最要紧的真相:**模型没有一颗连续的心。**每生成一个词,都是一次全新的、无状态的计算;两步之间,模型内部什么也不留下。生成上一个词的它与生成下一个词的它,像两位交接班的医生,彼此没有私人记忆;它们唯一的连续性,是那本摊在桌上的病历——KV Cache。铁打的病历,流水的医生。机器不是没试过像人一样记。上一代的循环神经网络(RNN)走的正是内化路线:把听过的一切,压缩进一团固定大小的脑内印象,像一位全凭记忆行医的老大夫。结局是:聊得越久忘得越多,久远的细节严重失真。Transformer 的选择恰恰相反——放弃好记性,改用笨而全的病历,把每句话的 k、v 原封不动记在体外。注意力机制的胜利,本质上是烂笔头对好记性的胜利。那这本病历有多重要?设想连病历也不写:每接一句新话,值班医生都得请老周把三年的病史从头重讲一遍,重新整理出每句话的 k 与 v,才能接住眼前这一句。接第十句,重讲九句;接第一千句,重讲九百九十九句——工作量以平方的速度膨胀。你在医院多半经历过换个医生,病史重讲一遍的烦恼;机器若不做记录,是每写一个字,都重讲一遍。所以解法自然得不像发明:边听边写病程里按序出场的每一句话——主诉、症状、化验结果——各自提炼一次 k(索引标签)与 v(详情),记进病历;新话来时,旧条目只翻,不重做。这就是 KV Cache 的全部:算过的 k 与 v,记下来,只增不重算, 更妙的是,这条规矩在病历里本就是法定的:《病历书写基本规范》明文禁止涂改,修改必须划线留痕、原文可辨——前篇说写入即定格还是修辞,在这里,是白纸黑字。那 q 呢?前篇说过,需求是一次性的。值班医生此刻心里的疑问——“现在该不该加药?”——回应完就翻篇了;病历记录事实,不记录她每一刻的内心检索。所以q 从不入档:缓存 K 和 V,丢弃 Q,这套机制的名字由此而来。图一 · 缓存的理由:重算的洪水,变成一条新记录第贰章 · 两种节奏,与记忆的账单prefill、decode,与一条记录的重量有了病历,行医就分成了两种节奏。入院那夜:通读与批量归档。老周当年从外院转来,带着一摞既往资料——对话系统里,这就是你的长提示词。接收他的值班医生不是一句句慢慢看,而是连夜通读、成批整理归档:所有条目可以并行去做,考验的是脑力。这个阶段术语叫prefill(预填充),它是计算密集的,也是首字慢的全部原因:第一条医嘱下达之前,她正在为你的全部前文归档。之后的病程:逐条翻检。正式的诊疗一句一句推进,每句只需新记一条——但每下一条新医嘱,都要把病历从头翻一遍。此时瓶颈换了:不是算,而是搬。把病历从架上取下、逐页过手的时间,超过了思考本身。这个阶段叫decode(解码),术语说它是访存密集的;你看到 AI 逐字连珠、却总快不上去,是她在一遍又一遍地翻病历。再算一笔账:一条记录有多重?每个词的 k、v,要在每一层、每个注意力头里各记一份。以主流的 7B 模型为例:2(k 和 v)× 32 层 × 32 头 × 128 维 × 2 字节,每个词约 0.5MB。一段四千词的病程,光记住前文就要压上约 2GB;一块 24GB 的消费级显卡,刨去模型本体(7B 半精度约 14GB),留给病案室的不过 10GB——满打满算,只够同时记住两万来个词,还是全院病人共享的额度。房租之贵,至此具体。第叁章 · 一整格架位的浪费vLLM 之前的病案室额度这么紧,旧的档案规矩却大手大脚。vLLM 之前,主流系统的做法是:病人一入院,病案室就划出一整格连续的架位,按这段病程可能达到的最长长度预留——比如两千条的位置。理由很工程:连续的空间,翻起来最快。浪费随之而来,而且有三种。其一,多数病程很快就结束了,预留的架位大半从未用过;其二,就算住得久,也很少恰好写满上限,零头空着;其三最隐蔽——两格架位之间的零碎空隙,拼不出下一位病人需要的完整一格。术语分别叫:预留浪费、内部碎片、外部碎片。浪费到什么程度?vLLM 团队 2023 年实测:当时的主流推理系统里,病案室真正放着病历的部分只有两到四成,其余六到八成被空架位锁死。显存是整台机器最贵的地段,而最贵的地段,浪费最凶。图二 · 三种浪费:预留、内部碎片、外部碎片第肆章 · 分页革命PagedAttention:活页病历,与一本目录换个规矩——而这一次,现实早就给出了答案:真实的病历,本来就是活页的。一页一页的活页纸,角上印着页码,首页之外另有一本目录。PagedAttention 做的,就是把这套民间智慧搬进显存:把记忆切成标准的活页,每页固定写十六个词的 k、v,写满一页领新页,哪里有空放哪里;另立一本病案目录(术语叫块表,block table),记着老周的第 1、2、3、4 页,分别放在 7 号、1 号、5 号、2 号架位。调阅时按目录把页码串起来读——逻辑连续,物理零散。追根溯源,这套办法来自操作系统的虚拟内存分页:你的电脑同时管理成百上千个程序的内存,靠的正是同一本目录。2023 年,Kwon 等人把它移植到 KV Cache 上,命名为PagedAttention,并据此写出了推理引擎vLLM。效果立竿见影:空置率从六成降到不足 4%,同一块显卡的吞吐量比当时最强的系统再高二到四倍——不是靠算得更快,纯粹靠不浪费。图三 · 学术版对照:PagedAttention 的块表映射(重绘自 Kwon 等,SOSP 2023)顺带一提,人脑存放记忆,恰恰也不是一卷连续的胶卷:心理学的经典结论是,记忆零散分布,回忆是一次按线索的重构——巴特莱特在 1932 年就用实验演示过这一点。必须连续的执念,原是机器的一厢情愿;分了页,机器的记忆术反倒离人近了一步。第伍章 · 共享的经济学一份文书、一场会诊、一本借阅登记分页之后,更漂亮的事情成为可能:共享。一份文书,全院只存一份: 每位病人的病历开头,都躺着几份一模一样的东西:知情同意书、入院须知、临床路径的标准模板——对话系统里,这叫系统提示词,常常几百上千词,人人相同。旧规矩给每本病历都抄一份;分页之后,聪明的做法显而易见:全院只存一份母本,所有病案目录都指向它。这就是前缀缓存(prefix caching),它也解释了一个体感:开头相同的对话,第二次的首字快得多——那几页,早就归档好了。一场会诊,分岔处才复印。会诊时常做推演:如果上手术,病程会怎样走?如果保守治疗呢?两套方案在纸面上同时展开——机器里,这叫并行采样或 beam search。两条分支共用分岔点之前的所有页:化验单、影像报告,一份就够;只有当某条分支要往共享的页上续写时,才先复印那一页、再往副本上写。术语叫写时复制(copy-on-write):能共享就共享,非改不可,才付复印费。一本借阅登记,决定何时腾退: 每一页共享的文书,都挂着一条借阅登记:还有几本目录正引用着我。病程结束,目录注销,计数减一;归零,架位才真正腾出。这里要替前篇补一笔坦白:前篇说缓存只增不改、写入即定格,在一段病程内部为真,甚至是法条;但对话终了,这本病历所占的架位就要整体归还——现实中的病历要封存三十年,显存的房租可等不起三十年。唯一的例外,是仍被人引用着的共享页。引用计数归零,内存回收;一段记录真正的离开,是最后一个需要它的人,注销了借阅。图四 · 共享的两种形态:前缀缓存,与写时复制第陆章 · 遗忘的工程学架子终究会满老周随诊到第三年,病历终究厚过了架子。最直觉的办法是只留最近的:设一个滑动窗口,新页进来,最旧的页按序腾退。2023 年,一项叫 StreamingLLM 的研究试了这个办法,撞上一个诡异的现象:中间的页怎么腾都行,模型照常工作;可一旦抽掉最前面那几条,整段理解当场崩塌,输出瞬间变成胡言乱语。研究者回头一看,正是前篇提过的那个现象——attention sink:所有后来的词,都把一部分注意力牢牢锚在最初几个词上;抽掉锚点,后面的每一页都读不懂了。解法随之而来,而病案室里的人一点也不会意外:首页永不抽离,加上一段最近的窗口——病案管理的老规矩,首页本就是整本病历的坐标。守住它,模型便能在理论上无限长的对话里稳定工作。翻译回病房:中途的病程可以精简,首诊的记录不能动。中途的页可以放下,最初的锚不能拔。图五 · 遗忘的设计:守住首页,滑动其余如果架子还是不够,工程师还有两招给病历瘦身:让多组注意力头共用同一套 k、v(术语叫 GQA,Llama 系列的标配)——与其给每双眼睛各配一本抄本,不如几双眼睛合看一本,架位占用直接除以几倍;或者把字写小一号——量化到 8 位甚至 4 位精度,笔迹略糊,但认得出。省下来的每一格,都能多收一位病人。第柒章 · 比喻的边界它哪里真,哪里假照例,收尾之前把账算清:这间病案室的比喻,哪里逼真,哪里撒谎。真得惊人的三处: 其一,轮班制让无状态有了现实原型:接班的医生确实只能依靠病历——铁打的病历、流水的医生,与逐词无状态的前向传播严丝合缝。其二,只增不改在病历里是白纸黑字的法定规范,前篇的修辞在这里落了地。其三,活页病历加病案目录,本就是分页 块表的民间版本——现实走在算法前面。撒谎的三处: 其一,真实的医生下医嘱不会重读全本病历,只翻相关的段落;机器是那位把制度执行到字面的笨医生——全量翻检、绝不抄近路,而这份笨的回报,是绝不遗漏,且可以并行。其二,一页十六条仍是工程粒度,真实的病历页可写不了这么整齐。其三,真实的医疗里,还有病历装不下的部分——查房时的直觉、对老病人的挂念——比喻同样装不下。方向也照旧说明:这一篇仍然是借人间的制度讲机器;对应之处是比喻的运气,不是医学的证据。尾声 · 灯火通明的病案室把全篇的比喻折叠成一句话:记忆不是拥有,是租用。每一页病历都占着一格昂贵的架位;每一份被共享的文书,都有人数着还剩几条借阅;每一次遗忘,都是一次腾退——而无论怎么腾,最先被守住的,永远是首页。而剥去全部比喻,机制的素颜不过如此:KV Cache 把算过的 k、v 存下,只增不重算;prefill 并行归档、decode 逐条翻检,前者拼算力、后者拼搬运;PagedAttention 把缓存切成定长块,用块表把逻辑顺序映射到零散的物理块,碎片近乎归零;相同前缀共享物理块,引用计数管理回收,写时复制处理分岔;放不下时,守住最初的 token,滑动其余。这段话如果你能一口气读顺,那么 vLLM 为什么快、API 为什么按上下文计价、首字为什么慢——你已经全部看懂了。最后,留两幅画面给你。下一次,第一个字迟迟不出,你可以确切地知道:一位值班医生正连夜通读你的转院材料,批量归档;之后逐字连珠,你也可以确切地知道:每写下一条,她都把你全部的病历翻了一遍。深夜的病案室,灯还亮着。今晚值班的,是小雨。(只为讲清楚大原理, 细节有很多不准确的地方, 想要完整准确了解还是请大家去看看论文和推导哦)(完)延伸阅读Kwon, W. 等,《Efficient Memory Management for Large Language Model Serving with PagedAttention》,SOSP,2023。PagedAttention 与 vLLM 的原始论文。Xiao, G. 等,《Efficient Streaming Language Models with Attention Sinks》,2023。守住首页 滑动窗口的出处。Vaswani, A. 等,《Attention Is All You Need》,NeurIPS,2017。一切的起点。博尔赫斯,《博闻强记的富内斯》,1942。记住一切的代价。巴特莱特,《记忆:一个实验的与社会的心理学研究》,1932。重构记忆的经典。原卫生部,《病历书写基本规范》,2010。只增不改的人间法条。