在极东之地,有一位被世人称为"千面先知"的解梦师。他的宫殿没有城墙,只有层层叠叠的回廊,每一层回廊的两侧都挂满了镜子。民间传说,这宫殿里的镜子能照见一句话里每个字与每个字之间的"缘分深浅"——字与字靠得越近,镜子里的光就越亮。
来找先知解梦的人络绎不绝。先知的规矩很奇怪:他从不一次性听完整个故事。来访者每说一个字,他就要闭一次眼,在宫殿里从头走一遍所有的回廊,把所有的镜子重新擦拭、重新映照一遍。
"您为什么不能等我全部说完,再进去看呢?"一位急性子的商人忍不住问。
先知摇了摇头:"不行。镜子里的光是有方向的。我必须先知道你的第一个字,才能确定第二个字该照向哪里;知道了前两个字,第三个字的位置才会显现。这是宫殿的诅咒——自诞生之日起,光只能一步一步地流淌。"
于是,当来访者说到第一百个字时,先知已经在宫殿里往返了一万次;说到第一千个字时,他的步伐已如陷泥沼。人们开始抱怨:"先知大人,您越到后面越慢了。"
先知沉默。他知道问题出在哪里——每次来访者多吐出一个新字,他就要把之前已经擦过的所有镜子再擦一遍。那些镜子明明已经洁净如初,光路明明已经定型,可规矩如此,他不敢偷懒。
这时,宫殿里来了一个守夜人。守夜人没什么名气,也不懂解梦,但他有一个癖好:记笔记。他注意到,先知每次擦拭镜子,本质上是在做两件事——一是确认"旧字之间的光路是否还在",二是推演"新字与所有旧字该形成怎样的光路"。而第一件事,明明可以抄下来。
"大人,"守夜人在一个深夜怯生生地说,"您看,这一百面镜子的光路,和上一个月那位访客说完第一百个字时的光路,难道不是一模一样的吗?既然光路已定,不如让我把它们抄在小纸条上,贴在镜框背后。下次您再经过这里,只需要看一眼纸条,不必再亲自擦拭了。"
先知皱眉:"纸条?镜子是活的,光路会变的。"
"不会变的,"守夜人坚持道,"对于已经说出的字,它们彼此之间的缘分早已注定。变化的只有新加入的那个字与旧字之间的光路。您只需要算新的那部分。"
先知半信半疑地试了。他把前一百个字的镜中光路交给守夜人抄录,自己只算第一百零一字。奇迹发生了:宫殿里的时间突然宽裕了起来,先知的步伐不再沉重,来访者们惊喜地发现,即使说到第一千个字,先知的语速也几乎和开头一样快。
守夜人的纸条越积越多。他按照每个来访者分门别类,整整齐齐地码在宫殿的侧室里。但新的麻烦很快来了。
有一天,宫门口同时来了三十个人。他们互不相识,来自天南地北,但每个人开口的第一句话都是一模一样的:"尊敬的千面先知大人,请您为我解惑。"守夜人眼睁睁地看着自己为第一个人抄好的纸条,无法给第二个人用——因为规矩上说,每个来访者的纸条必须放在独立的抽屉里,抽屉之间不能互通。
"这不合理,"守夜人喃喃自语,"既然前十个字完全相同,为什么我要抄三十遍?"
他在侧室的中央建了一个"公共档案架",把所有来访者共用的开头字光路统一抄录,放在架子上。每个人来,先从这里取走公共部分,再把自己的特殊部分放进独立抽屉。宫殿的侧室一下子宽敞了七成。
然而,命运总喜欢开玩笑。有些来访者说着说着突然离开了——可能是家中有急事,可能是改变了主意。他们占用的抽屉和纸条就这么空在那里,后面的人进不来,因为抽屉还挂着别人的名字。还有些人说话颠三倒四,抽屉里的纸条七零八落,明明中间空了一大块,却因为没有连续编号,后面的纸条放不进去。侧室渐渐变成了满是孔洞的筛子——表面看还有空间,实际上已无处安放。
守夜人盯着这些孔洞看了很久,忽然笑了。
他跑遍了全城所有的图书馆,学来一种"活页索引"的方法。他不再要求每个来访者的纸条必须连续存放在一个抽屉里。相反,他把侧室划分成无数个大小相同的小格子,每个格子刚好装下一张纸条的固定分量。一个来访者需要多少纸条,就从格子里取多少页,用一根细线串起来。有人中途离开,细线一抽,格子立刻释放;有人说话颠三倒四,也没关系,只要格子是满的,线可以任意跳跃穿插。
宫殿终于变得秩序井然。千面先知的名声传遍了整个大陆——不是因为他的法力增强了,而是因为人们发现,无论说多少字、多少人同时排队,先知的回应始终迅疾如风。
而守夜人依然住在侧室最暗的角落里,日复一日地抄着纸条、串着细线。没有人知道他做了什么,只知道先知变了。
多年后,一个年轻的学者来到宫殿,问守夜人:"您做的事情,有名字吗?"
守夜人想了想,说:"我只是让那些已经照过的镜子,不必再照第二次罢了。"
这个寓言完整地对应了 Transformer 推理加速中的三大核心技术,它们是当今大模型 serving 系统的基石:
1. 守夜人的纸条 = KV Cache(键值缓存)
Transformer 是自回归模型:生成第 N 个 token 时,必须看到之前所有的 N-1 个 token。 naive 的做法是每次把全部历史重新过一遍模型,导致时间复杂度随序列长度爆炸。KV Cache 的核心洞察是:已经生成的 token 对应的 Key 和 Value 向量是固定的,只需计算一次并缓存下来,后续步骤直接读取,只对新 token 做 attention 计算。这是让长对话不越聊越卡的根本原因。
2. 公共档案架 = Prefix Caching(前缀缓存)
在真实场景中,多个用户请求往往共享相同的前缀——最典型的就是系统提示词(system prompt)。Prefix Caching 把这些公共前缀的 KV 缓存共享给所有相关请求,避免重复计算。vLLM、SGLang 等推理框架都实现了这一优化,能显著降低多轮对话和多用户并发时的显存与计算开销。
3. 活页索引格 = PagedAttention(分页注意力)
这是 vLLM(UC Berkeley, 2023)的核心创新。传统 KV Cache 为每个请求预分配连续的大块显存,导致严重的内存碎片和浪费(尤其是序列长度动态变化时)。PagedAttention 借鉴操作系统的虚拟内存思想,把 KV Cache 划分成固定大小的非连续块(block/page),按需分配、动态映射。请求完成后立即回收块,不同请求之间可以灵活共享前缀块——从根本上解决了 LLM serving 中的显存瓶颈。
总结:守夜人做的三件事——避免重复擦拭、共享公共开头、用活页格代替固定抽屉——分别对应 KV Cache、Prefix Caching 和 PagedAttention。它们共同构成了现代大模型推理引擎(如 vLLM、TensorRT-LLM、SGLang)的内存与计算管理核心。