当前位置: 首页 > news >正文

显存不是只看够不够:LLM 推理中的显存碎片、分配器与 KV Cache 页管理工程拆解

很多团队做 LLM Serving 时,第一反应总是模型多大、量化到几 bit、KV Cache 占多少显存、单机能扛多少并发。但线上系统真正跑起来之后,经常遇到一种更难受的问题:明明总显存看起来还够,服务却还是 OOM、吞吐抖动、TTFT 变长,甚至越跑越不稳定。

这背后往往不是“显存总量不足”这么简单,而是显存碎片、动态分配策略、KV Cache 管理方式、请求调度模式一起作用的结果。你看到的是一次分配失败,真正的问题可能是页块太碎、长短请求混跑、block 回收不及时、prefix 复用带来生命周期错配,或者 allocator 和 serving runtime 的粒度设计不一致。

本文从工程视角系统拆解一个常被低估、但又极其影响 LLM 稳定性的主题:LLM 推理为什么不能只盯着“剩余显存”,还必须理解分配器、碎片、页式 KV 和调度之间的耦合关系。如果你在看 vLLM、TensorRT-LLM、SGLang、PagedAttention、prefix cache 或推理稳定性问题,这篇文章应该能把很多零散概念串起来。

目录

  1. 为什么线上最难的问题常常不是“显存不够”,而是“显存不好用”
  2. 先把账算清:LLM 推理显存到底花在了哪里
  3. 显存碎片到底是什么,它为什么会让服务越来越不稳
  4. 为什么 LLM Serving 比普通推理更容易产生碎片
  5. 连续 KV Cache 为什么在真实服务里很快失去优势
  6. PagedAttention 和 block manager 到底解决了什么
  7. 分配器视角:你以为在分 token,其实是在分生命周期
  8. 调度为什么会直接影响显存稳定性
  9. Prefix Cache、长上下文和多轮对话为什么会放大问题
  10. vLLM、TensorRT-LLM、SGLang 应该分别怎么理解这件事
  11. 线上排障时该盯哪些指标,而不是只看 nvidia-smi
  12. 几种最常见、也最容易误判的工程场景
  13. 工程落地建议:怎么把“能跑”变成“稳跑”
  14. 面试里怎么把这个题讲得像真的做过
  15. 总结

1. 为什么线上最难的问题常常不是“显存不够”,而是“显存不好用”

很多人刚接触 LLM 推理时,会默认把显存问题理解成一道静态容量题:

  • 模型权重占多少
  • KV Cache 占多少
  • batch size 能开多大
  • 剩余显存还能不能再塞请求

这种理解在离线 benchmark 里通常没问题,但在线服务不是静态世界。真实流量会同时带来:

  • 不同长度的 prompt
  • 不同长度的生成
  • 请求随时进入和退出 batch
  • 多轮对话造成长生命周期 KV
  • prefix cache 造成跨请求复用
  • 调度器不断重排、插队、抢占和回收

于是显存问题很快从“容量不足”变成“资源形状不合适”。

一个 GPU 明明还剩 8GB 空闲显存,却可能仍然放不下一个新的长请求;不是因为总量不够,而是因为可复用的空闲块被切得太碎,或者某些 KV block 虽然逻辑上空闲,物理上却还无法及时回收。

这就是为什么很多团队会遇到一种非常典型的现象:

  • 压测一开始吞吐正常
  • 跑久了延迟开始抖
  • 再往后偶发 OOM
  • 重启服务后一切恢复

如果重启能明显“治好”问题,十有八九就不能只盯模型大小,而要开始怀疑碎片、回收和分配策略。


2. 先把账算清:LLM 推理显存到底花在了哪里

讨论碎片之前,先要把显存账本建立起来。对一个 LLM Serving 进程来说,显存大致花在下面几类地方:

  • 模型权重
  • 临时激活和中间 buffer
  • attention / GEMM 等 kernel workspace
  • 通信 buffer
  • KV Cache
  • runtime 元数据和索引结构
  • allocator 自身的保留与缓存

其中最容易被低估的是最后两项。

很多人觉得“显存就是权重加 KV”,但真实系统里往往还存在:

  • 框架级缓存分配器保留的显存
  • CUDA graph / execution plan 相关 workspace
  • 不同 shape 触发的临时 buffer 扩张
  • block table、page table、索引映射等元数据

如果你只算理论 KV 容量,最后很容易得到一个过于乐观的并发预算。

更重要的是,权重通常是静态的,KV 和临时 buffer 才是动态的。真正制造不稳定性的,往往不是静态部分,而是动态部分的生命周期高度不规则。


3. 显存碎片到底是什么,它为什么会让服务越来越不稳

显存碎片本质上不是“总量减少”,而是“可用空间被切散了”。

可以把它想象成酒店房间管理:

  • 你总共有 100 个房间
  • 现在空着 20 个
  • 但这 20 个分散在很多层、很多边角
  • 来了一支需要连续 8 间房的团队,结果接待不了

显存分配也是类似道理。一次新的分配请求不只关心“还有多少空闲”,还关心:

  • 空闲块是否足够大
  • 块大小是否匹配
  • 生命周期是否能和已有对象兼容
  • 是否会触发额外的搬迁或回收

在线 LLM 服务里,碎片的危害主要有四个:

  1. 新请求准入失败,出现 OOM 或 admission failure
  2. 调度器被迫保守,吞吐下降
  3. 回收动作变频繁,延迟抖动增大
  4. 长时间运行后稳定性下降,服务表现出“老化”特征

所以碎片不是一个“底层实现细节”,而是直接影响 SLA 和成本的线上问题。


4. 为什么 LLM Serving 比普通推理更容易产生碎片

传统 CV/NLP 推理很多是静态 shape、固定 batch、短生命周期 tensor。即使有动态 shape,生命周期也相对整齐。

但 LLM Serving 有几个天然更容易制造碎片的特点。

4.1 请求长度高度不一致

一个 256 token 的请求和一个 16K token 的请求,会消耗完全不同规模的 KV 空间,而且释放时间也不同。

4.2 decode 是持续增长型内存

随着生成继续,每个请求的 KV 会不断追加。它不是“一次分完一次释放”,而是边跑边长。

4.3 continuous batching 让生命周期交错

请求不断进入和退出 batch,显存对象的生死顺序被打乱,天然不利于形成整齐的分配/释放模式。

4.4 prefix cache 和复用带来共享引用

某些 KV 不再只属于单个请求,而会被多个请求共享。这会让回收条件更复杂,不再是“请求结束就释放”。

4.5 长对话和长上下文容易形成“长尾占坑”

少量超长会话可能长期占住大块 KV 空间,而大量短请求在旁边频繁进出,最终把空闲区域切得很碎。

所以 LLM Serving 的内存管理,本质上不是简单的 tensor 分配,而是一个带有明显系统特征的在线资源管理问题。


5. 连续 KV Cache 为什么在真实服务里很快失去优势

从直觉上看,连续内存似乎总是更好:

  • 地址简单
  • 访问局部性好
  • 元数据更少
  • 看起来更“规整”

这在单请求、固定长度、离线推理场景里可能成立。但到了多租户在线服务,连续 KV 往往很快暴露两个问题。

5.1 预留过大会浪费

如果你为了避免扩容,给每个请求预留较大的连续空间,那么短请求会造成大量内部浪费。

5.2 动态扩容很困难

如果一开始只给较小空间,后续生成变长时就可能需要扩容。连续内存一旦后面没有足够空间,要么搬迁,要么分配失败。

这就是连续 KV 的核心矛盾:

预留太多会浪费,预留太少会扩不动。

而真实 LLM 服务恰恰最不确定的就是请求最终会长到哪里。你没法在请求进入时,准确知道它会生成 50 token 还是 2000 token。

所以连续 KV 更像是静态世界里的好方案,而不是动态世界里的稳方案。


6. PagedAttention 和 block manager 到底解决了什么

PagedAttention 被广泛提起,但很多人只记住了“把 KV 分页”,没有真正理解它解决的是哪一层问题。

它的核心思想可以概括成一句话:

不要要求每个请求的 KV 在物理显存中连续,而是把它拆成固定大小的 block,通过映射表组织成逻辑连续。

这样做的直接收益有三类。

6.1 降低外部碎片

请求不再需要申请一整段大块连续空间,而是按 block 逐步增长。只要还有足够 page/block,就能继续扩展。

6.2 提高回收灵活性

请求结束后,可以按 block 回收;共享前缀场景下,也可以基于引用计数精细释放。

6.3 让调度和内存更容易协同

调度器可以按 block 预算做 admission control,而不是只看粗糙的“剩余显存百分比”。

但分页不是白送收益。它也会带来:

  • page table / block table 元数据开销
  • 地址映射与 gather 成本
  • block size 选择的 trade-off
  • 内部碎片问题从“连续段浪费”变成“页内浪费”

所以 block manager 的价值不只是“把 KV 管起来”,而是为在线服务提供一个更适合动态生命周期的资源抽象


7. 分配器视角:你以为在分 token,其实是在分生命周期

很多人谈 KV 管理时只盯 token 数量,但 allocator 真正在意的是对象生命周期。

为什么?

因为如果两个对象大小类似,但生命周期完全不同,它们混放在同一类资源池中,就很容易制造碎片。

例如:

  • 短请求的 decode block,生命周期可能只有几百毫秒
  • 长对话的 block,生命周期可能持续几十秒甚至几分钟
  • prefix cache block,可能跨多个请求共享
  • workspace buffer,可能在一次 kernel 执行后立刻释放

如果这些对象全都走同一套粗粒度分配路径,系统很容易出现:

  • 短命对象频繁打洞
  • 长命对象占住关键位置
  • 回收节奏和申请节奏不匹配

因此成熟系统往往会引入分层管理思路:

  • 静态权重与动态 KV 分离
  • KV block 与临时 workspace 分离
  • 可共享前缀与私有 decode block 分离
  • 不同大小等级的 block 分桶

从 allocator 角度看,真正高级的优化不是“把显存榨干”,而是让不同生命周期的对象尽量少互相污染


8. 调度为什么会直接影响显存稳定性

很多人把调度问题和显存问题分开看,这在 LLM Serving 里经常是错的。

调度器决定了:

  • 哪些请求一起进入 batch
  • 谁先 prefill,谁先 decode
  • 长请求和短请求是否混跑
  • 是否允许超长请求占满 page pool
  • admission control 按 token、按 block 还是按请求数

这些决策会直接改变显存对象的生命周期分布。

一个很典型的例子是长短请求混跑:

  • 短请求快速进入、快速退出,频繁申请/释放 block
  • 长请求持续占住大块 KV,不断追加 page
  • 最终 page pool 虽然总量未满,但高质量空闲块越来越少

再比如 prefill burst:

  • 突然来一批超长 prompt
  • prefill 阶段瞬间吃掉大量 block
  • decode 侧被迫让路
  • TTFT 和吞吐同时恶化

所以优秀的调度器不只是“排队器”,而必须具备基本的显存意识。至少要回答三个问题:

  1. 当前 admission 看的是 token、block 还是 bytes
  2. 哪类请求会放大碎片和抖动
  3. 遇到资源紧张时,是限流、降级、分池还是抢占

9. Prefix Cache、长上下文和多轮对话为什么会放大问题

Prefix Cache 看起来是在省算力,但它同时也改变了内存对象的生命周期。

9.1 prefix 复用会让“该释放”的东西不再容易释放

如果一个前缀被多个请求共享,那么它的释放时机就从“请求结束”变成“最后一个引用离开”。这会带来更长的驻留时间。

9.2 长上下文会放大 block 占用

上下文越长,单请求的 page 链越长;一旦并发上来,page pool 很容易被少量长请求吃掉。

9.3 多轮对话天然偏向长生命周期

会话式应用不像一次性问答。它会不断追加历史上下文,导致部分 KV 在系统中停留得更久,更像“热状态”而不是“临时缓存”。

9.4 RAG/Agent 会让前缀复用更不稳定

工具定义、检索片段、system prompt、格式模板可能部分固定、部分动态。这意味着前缀不是“完全不复用”,也不是“完全复用”,而是会在边界处形成很多不规则对象。

这就是为什么很多团队做完 prefix cache 后,会发现命中率、显存占用和回收行为之间出现新的 trade-off。它省掉了重复 prefill,但也引入了更复杂的状态驻留和释放逻辑。


10. vLLM、TensorRT-LLM、SGLang 应该分别怎么理解这件事

这几个系统不应该只从“谁更快”来理解,更应该从它们对推理运行时抽象的选择来理解。

10.1 vLLM

vLLM 最被广泛讨论的,就是 PagedAttention 和基于 block 的 KV 管理。它的价值在于把“动态请求 + 动态长度 + 多租户共享”这个问题显式拉到 runtime 设计层面。

如果你在看 vLLM,重点应该理解:

  • block/page 抽象如何降低碎片
  • block table 如何组织逻辑连续性
  • 调度器如何围绕 block 预算做准入和执行
  • prefix sharing 如何影响回收和引用计数

10.2 TensorRT-LLM

TensorRT-LLM 更强调 engine、kernel、图优化、量化和执行效率。它在高性能执行路径上很强,但工程上同样绕不开 KV 管理、batching 和资源预算问题。

理解 TensorRT-LLM 时,不要只盯算子性能,还要看:

  • engine 对动态 shape 的约束
  • workspace 和 execution buffer 的预算
  • 长上下文和 paged KV 的支持方式
  • 性能最优配置和资源最稳配置是否一致

10.3 SGLang

SGLang 的价值常体现在执行语义、structured generation、prefix / state 复用与服务编排能力上。它让“状态”变得更显式,因此也更需要认真处理状态驻留、复用收益和资源隔离。

理解 SGLang 时,重点不只是 API,而是:

  • 状态复用如何影响显存生命周期
  • 多阶段执行如何影响 prefill / decode 资源切换
  • 应用层编排是否会制造新的长生命周期对象

把这三个系统放在一起看,你会发现它们都在回答同一个问题:如何把不规则请求流量变成可管理、可预测的运行时资源行为。


11. 线上排障时该盯哪些指标,而不是只看 nvidia-smi

nvidia-smi很重要,但远远不够。

如果你真的在定位显存碎片和 KV 管理问题,至少要建立下面这组观测。

11.1 容量类指标

  • 权重占用
  • KV 占用
  • workspace 占用
  • allocator reserved bytes
  • allocator active bytes
  • 可分配 block 数

11.2 碎片类指标

  • 空闲 block 总数
  • 最大连续可用块
  • 不同 block size 桶的空闲分布
  • 页内浪费比例
  • block 分配失败次数

11.3 生命周期类指标

  • 请求平均存活时间
  • 长请求占比
  • prefix cache 对象平均驻留时间
  • block 引用计数分布
  • 回收延迟

11.4 调度类指标

  • admission 拒绝率
  • prefill 等待时间
  • decode 等待时间
  • active sequence 数
  • 连续 batching 的 batch occupancy

11.5 用户体验类指标

  • TTFT
  • TPOT / tok/s
  • P50 / P95 / P99 延迟
  • OOM 次数
  • 服务重启前后的性能差

如果没有这套指标,很多团队会在“是模型太大”“是流量太高”“是 GPU 不够”“是框架不稳定”之间来回猜,最后根本定位不到根因。


12. 几种最常见、也最容易误判的工程场景

场景一:总显存没打满,但 admission 持续失败

这通常不是“还有优化空间”,而可能是:

  • 空闲块不够连续
  • block 池被长请求占住
  • prefix 共享对象释放滞后
  • allocator reserved 过高但 active 不高

场景二:压测一小时后延迟明显变差

这类问题经常和运行时老化有关。常见原因包括:

  • 长短请求混跑导致 pool 质量下降
  • 小对象频繁分配释放制造碎片
  • block 回收路径慢于申请路径
  • 缓存命中提升了驻留,但没有同步升级淘汰策略

场景三:量化后理论上省了显存,但并发没涨多少

可能的原因是:

  • 节省的是权重空间,不是动态 KV 瓶颈
  • workspace 或 page pool 才是上限
  • 调度策略过于保守,没有把新增空间转化成更多 admission

场景四:Prefix Cache 命中率提升了,但 OOM 更频繁

这听起来矛盾,其实很常见。因为命中率高意味着更多共享状态被保留,如果回收和淘汰策略没跟上,驻留时间会变长,最终挤压私有 decode 空间。

场景五:重启服务后指标立刻恢复

这类问题一定要优先检查:

  • allocator 缓存膨胀
  • pool 碎片累积
  • page 回收不及时
  • 长尾会话导致的状态滞留

如果每次重启都“治标有效”,那就说明问题高度可重复,只是之前没有合适的指标把它看见。


13. 工程落地建议:怎么把“能跑”变成“稳跑”

如果你真的要把系统从 demo 推到生产,下面这些原则比单点 benchmark 更重要。

13.1 别只做静态容量预算,要做动态生命周期预算

除了算模型和理论 KV,还要估计:

  • 长短请求分布
  • 平均会话时长
  • prefix 复用带来的驻留增长
  • 峰值时段的 prefill burst

13.2 用 page/block 思维管理 KV,而不是连续段思维

在线服务里,灵活回收和渐进扩容,通常比“理论最规整布局”更重要。

13.3 给不同生命周期对象分池

至少考虑把:

  • 共享前缀
  • 私有 decode KV
  • 临时 workspace
  • 超长会话

做逻辑隔离或预算隔离,减少互相污染。

13.4 调度器要显存感知

不要只按请求数限流。更合理的是按 token、block、上下文长度、会话类型联合做 admission。

13.5 观测一定要能看到“运行时老化”

只看瞬时吞吐没有意义。至少要补:

  • 长时间 soak test
  • 混合长短请求压测
  • prefix 开关对比实验
  • 重启前后 allocator / block 分布对比

13.6 优化目标要从“跑满 GPU”改成“稳定兑现 SLA”

线上系统最怕的不是平均值不高,而是尾延迟失控、偶发 OOM 和不可预测抖动。对业务来说,可预测性往往比单次峰值更重要。


14. 面试里怎么把这个题讲得像真的做过

这是一个非常适合拉开差距的话题,因为它天然带有系统味道。

如果面试官问你“KV Cache 怎么优化”,很多人只会回答:

  • 做 PagedAttention
  • 做量化
  • 提高命中率

这样的回答太浅。更好的讲法应该是:

  1. 先区分静态显存和动态显存
  2. 说明线上瓶颈不只是容量,还有碎片和生命周期错配
  3. 解释连续 KV 为什么在动态请求下不稳
  4. 解释 page/block 抽象如何降低外部碎片
  5. 再补充调度、prefix cache、长会话如何改变回收行为
  6. 最后讲你会怎么看指标、怎么验证优化是否真的生效

如果再进一步,你甚至可以主动抛出这几个面试加分点:

  • 为什么命中率高不等于资源效率高
  • 为什么量化后并发不一定同步提升
  • 为什么重启后恢复正常往往指向碎片或 allocator 问题
  • 为什么 admission control 应该按 block/token 而不是只按请求数

能把这些 trade-off 讲清楚,面试官通常会判断你不只是看过论文,而是真的理解推理系统的工程约束。


15. 总结

LLM 推理里的显存问题,远不只是“够不够”这么简单。

更准确地说,它是四件事共同决定的结果:

  • 总容量够不够
  • 分配形状合不合适
  • 生命周期是否规整
  • 调度策略是否和内存抽象匹配

这也是为什么 vLLM、TensorRT-LLM、SGLang 这些系统虽然切入点不同,最后都会落到同一个核心矛盾上:

如何让高度不规则的请求流量,在有限 GPU 显存上以足够稳定、足够高效、足够可预测的方式运行。

如果你正在做 LLM Serving,建议尽早把“显存碎片、分配器、KV 页管理、调度耦合”作为一个完整专题来看。它不是边角知识,而是决定系统能否长期稳跑的主干能力。

很多时候,真正把服务从“能 demo”拉到“能生产”的,不是再多压出 5% 峰值吞吐,而是先回答清楚:

为什么这台 GPU 明明还有显存,系统却已经开始不敢接请求了。

http://www.cnnetsun.cn/news/3197394.html

相关文章:

  • AI第四次范式转移:从Prompt到Loop,普通人如何落地循环机制
  • 环境变量LD_LIBRARY_PATH解析
  • 【Java毕业设计】基于 SpringBoot 的红色景点预约游览系统的设计与实现 基于 SpringBoot 的红色旅游资讯推广平台(源码+文档+远程调试,全bao定制等)
  • NCP1015 反激变换器 PCB 布局 10 条黄金法则:从原理图到 6.5W 双路输出实战
  • AI和集成电路怎么选?2026届新工科保研别把赛道规划搞反了
  • 2026年GEO搜索优化究竟是什么?揭开这一神秘技术的真实面纱!
  • YAAW-for-Chrome:浏览器下载管理终极解决方案,告别命令行复杂操作
  • 【SkyWalking从入门到精通】第07篇:远程调试那些事——线上SkyWalking Agent的正确调试姿势
  • ANDANTEX有哪些核心产品?从自动化设备选型角度看精密传动产品体系
  • 立创EDA边框层绘制避坑指南:解决3类Gerber导出“边框未闭合”错误
  • 竞争存在论:存在的投影法则——三连续统框架下的认知与本质
  • 深入LangChain源码:Agent执行器与工具调用机制剖析
  • 本地AI大模型部署实战:从Ollama工具选型到DeepSeek模型集成
  • 8层高速PCB叠层设计实战:3种主流结构对比与±5%阻抗控制要点
  • 金蝶云星空 + 旺店通数据同步至飞书多维表格 BI 看板实施方案
  • 步进电机驱动器 3 种细分设置对比:Y2SSR4 从 1/1 到 1/32 的精度与速度实测
  • 基于YOLO11的驾驶员状态检测:从数据集构建到模型训练实践
  • 专知智库OPC研究院:将传统产业链拆解为OPC公司集群——从“大公司主导”到“OPC集群协作”的产业重构
  • ComfyUI整合包+KREA2模型:AI绘画本地部署全攻略
  • Miniasm-0.3 (r179)安装与使用--生信工具104
  • 职场考研MTT-MBA-MPA-MEM到底选哪个-2027科创从业者分流指南
  • 脚手架_结构及作用
  • 让中学生也能一下子认识中学数学有几百年重大错误:将无穷多各异假R误为R
  • 解密WinBtrfs:实现Windows与Linux文件系统无缝互通的创新方案
  • 企业级开源AI 智能代码评审系统,一键部署,真正解决代码质量问题
  • 打造全自动AI资讯聚合发布系统:liyown/ai-trend-publish项目深度解析与从配置到自动化部署的保姆级实操指南
  • 都来投米哈游!
  • DeepSeek using Claude Code 重构Oracle Fusion/SAP最佳业务实践:以add user account为例子,描述程序的访问序列
  • DTC诊断测试方法
  • 那个差点被裁的女设计师,三个月后带着爆款杀回来了