上篇讲到,一种很直接的做法是给每个请求按"可能的最大长度"预留一块连续的显存空间。这种传统预分配,最大的问题就是这块空间常常用不满。
这一篇就把这件事拆开看:KV cache 到底占了多少显存,传统分配又为什么让这么多显存白白空着,以及它为什么会成为企业级部署的瓶颈。
我在《大模型推理到了推理引擎这一步,到底要管哪些事》里讲过,KV cache 的空间分配、写入和读取,主要由推理引擎负责;
在《Decode 每轮都重算一遍历史 token 吗:聊聊 KV cache》里也带过,Decode 每轮都要把历史的 K 和 V 读出来参与注意力计算。
KV cache 为什么是个显存吞金兽
先说清楚一件事:
KV cache 不是存一点就完事,序列越长,要留在显存里的 K 和 V 就越多。
模型里有一层一层的注意力层,每一层都会为当前 token 算出 K 和 V 两个向量。
一个请求从输入 prompt 开始,到后续不断生成 token,每一层、每一个需要参与后续注意力计算的历史 token 的 K 和 V 都得留在显存里,下一轮 Decode 才不用重算。
所以 KV cache 的大小,基本等于:
序列长度 × 层数 × KV head 数 × head_dim × 2 × 每个元素的字节数
这里是为了说明量级的简化公式;
实际大小还会受到模型架构、KV cache 数据类型等因素影响。
序列越长,占的显存就越多,而且几乎是线性往上爬。
举个量级上的例子:
一个 70 亿参数级别的模型,每个 token 对应的 KV cache 可能就是几十 KB 到几百 KB,具体取决于模型的层数、KV head 数和 KV cache 数据类型;
一个请求跑到几千个 token,光 KV cache 就可能占掉几百 MB 甚至上 GB 的显存。
这还没算模型权重本身占的那一大块。
类比一下:
KV cache 像你记课堂笔记,每听一句话就往本子上添一行;
本子页数是有限的,话却越说越长,写着写着就不够用了。
那问题就来了:
既然它这么占地方,推理引擎到底是怎么给每个请求安排空间的?
最直觉的做法:按最大长度,提前留够
最直觉、最省事的办法,就是"按最坏情况预留"。
一个请求进来,引擎并不知道它最终会生成多少个 token。
如果采用按最大长度预分配的连续 KV cache,就可以直接按照模型允许的最大序列长度(比如 8192 个 token)预留一整块空间。
它的好处很简单:
后面 Decode 一路追加 K 和 V,空间永远够用,不用在生成中途发现不够了再去扩容、把已有的 KV cache 挪到别处,引擎实现起来最省心。
代价呢?
也恰恰藏在这个"按最大长度"里。
浪费从哪来:预留了,用不上,还独占
关键矛盾是:
绝大多数请求,根本到不了最大长度。
设想同一时刻有三个请求 A、B、C 一起进来。
引擎给 A 划了 8192 个 token 的 KV 空间,给 B、C 也是同样大小——因为"按最大长度"对所有请求一视同仁。
可 A 实际上只问了一个很短的问题,整个请求最终也就用了十几个 token 就结束了。
于是 A 那块 8192 的额度里,前面一小段写满了 K 和 V,后面绝大部分都空着。
空着不要紧,要紧的是:
这块空间已经被 A 占下了,别的请求用不了。
这些还没写入 KV 的空间,从内存管理的角度看,会形成严重的内部碎片。
越是短的请求,浪费的比例越高。
一个只需要十几个 token 就结束的短请求,却占着 8192 的额度,利用率连 1% 都不到;
旁边那个长文档摘要任务可能用到了 6000,也还是没填满。
如果请求平均只跑到最大长度的两三成,那么这部分预留 KV cache 的实际使用率也可能只有两三成。

低利用率,为什么让并发上不去
低利用率的代价,不只是浪费显存。
显存就那么多,KV cache 占掉一大半预留额度却空着,等于同一张卡能同时服务的请求数被硬生生压低了。
回到上篇说的批处理:
企业级推理靠把多个请求拼成一批来提吞吐。
可批处理的前提,是显存里能同时放下更多请求的 KV cache。
传统预分配下,每个请求都先占住一整块最大长度的 KV cache 额度,实际却只用其中一小部分,显存里能同时放下的请求数自然就少。
结果就是:
很多时候,单张显卡真正卡住并发的,不只是算力,而是 KV cache 的显存容量:
大量空间被提前预留却空着,新请求进不来,吞吐自然也上不去。
另外,在需要动态调整连续 KV cache 空间的分配方案里,还可能出现外部碎片。
请求进进出出,占用的连续空间大小不一,释放之后就在显存里散落成一些零碎的空闲区间。
这些区间即使加起来已经够放下一个新请求,也可能因为彼此不连续,凑不出一整块连续空间。
于是,总显存明明还有,新的请求却不一定塞得进去——这又进一步限制了并发。

显存被吃空,根源在传统分配
问题已经很清楚:
这里的核心问题,不是 KV cache 本身太浪费,而是那套"按最大长度一刀切预留连续空间"的分配方式。
真正需要解决的是 KV cache 的显存分配效率。
要破这个局,思路得换:
与其给每个请求锁死一整块大显存,不如把 KV cache 也像操作系统管内存那样,切成固定大小的小块,按实际用多少、动态分给谁。
这正是 PagedAttention、分块管理这一类解法想做的事——把"整块预留"换成"按需分页",大幅减少连续预分配带来的显存浪费,并缓解传统连续内存分配中的碎片问题。
到这儿,大模型推理核心原理这一阶段就讲完了:
从 Prefill 和 Decode 两阶段、一次完整推理要过的几道工序、TTFT 和 ITL 两个速度指标,到 KV cache 为什么必须存在、推理引擎怎么把它们跑起来,一直到传统 KV cache 在显存上踩的坑。
网硕互联帮助中心






评论前必须登录!
注册