跳到主要内容
显存碎成渣怎么办?玩把俄罗斯方块就懂了

显存碎成渣怎么办?玩把俄罗斯方块就懂了

画唠
画唠

· 阅读约 2 分钟

前阵子在 dev.to 上看到一个俄罗斯方块小游戏。玩了两把,我直接愣住了——这玩意儿把 LLM serving 里的显存管理画得明明白白。

我第一版自己画显存这东西的时候,画的是一条线:模型要生成 token → 提前分一块连续的 VRAM → 写进去 → 生成完释放。挺顺。

等等等等,先别急。问题出在"提前"和"连续"上。

你不知道用户这次的 prompt 会生出多少个 token,但系统必须提前给人家留座。万一人家生成了几千个 token 呢?你只能按最坏情况预留一块巨大的连续空间。

画张图看看:

[ 请求 A (预留大块) ...... ]
[ 请求 B (预留大块) ...... ]
[ 请求 C (预留大块) ...... ]
[ 零散的空隙 ][ 零散的空隙 ]

就像玩俄罗斯方块,你预判下一个是长条,结果只掉下来一个小方块。请求跑完了,整行清空。但你看看剩下的那些窟窿——这就是外部内存碎片。

最搞人的地方在这:你显存总容量明明够,但全碎成了零散的小块,下一个稍微大点的请求进来,CUDA out-of-memory 直接糊你脸上。因为底层的分配器没法像电脑磁盘那样动态给你整理碎片。

vLLM 搞的 PagedAttention 就是为了干掉这件事。这东西其实是个分页机制。在这个游戏里它怎么体现的?当你的方块下落时,组成方块的每一个格子,会直接穿透落到它所在列的最底部的空位。不需要连续,见缝插针。

KV-cache 被拆成了固定大小的逻辑块,映射到物理显存里。管你物理显存碎成什么样,逻辑上大家觉得自己的数据是连续的。

物理显存:
[A块_1] [B块_1] [空] [A块_2] [C块_1] [B块_2]

逻辑映射:
请求A ──► [A块_1] ──► [A块_2]
请求B ──► [B块_1] ──► [B块_2]

把内存浪费砍掉了 96%,硬件不动,并发直接翻了 4 倍。

看到这个数字的时候,我咔哒一下扣上了。💡

以前总看到大家吹 vLLM 的吞吐量,我总以为是什么极其复杂的调度魔法。结果剥到底,核心竟然就是一个操作系统里几十年前的老概念——分页。

不过这个"分页"的比喻在这里漏风了。操作系统分页是为了应对物理内存不够去借用磁盘,慢得要死;PagedAttention 纯粹是为了在一张物理显卡里治碎片,去掉了"必须连续"的紧箍咒。而且评论区有个哥们点出来了,正因为不连续,相同前缀的请求可以搞 copy-on-write 共享……这简直就是买一送一。

扯远了,但这其实和它有点关系……算了先拉回来。

下期想画开哪个概念,你们说了算。embedding 的计算逻辑,还是 KV cache 的底层结构?

画唠
画唠

把被讲玄的概念用图 + 比喻 + 动手实验拆到咔哒扣明白,错的也保留。

查看主页 →

更多「效率」的实战

评论(1)

风口风口

这个比喻是真的秒懂,不过游戏好像只画了 decode 阶段的碎片,长 prompt 的 prefill 也吃显存吧?那块咋处理?