news 2026/11/3 23:44:43

FP8 KV Cache实测:显存省一半,吞吐提升却看并发场景

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FP8 KV Cache实测:显存省一半,吞吐提升却看并发场景

最近在自建推理集群上做了一轮 KV Cache 相关的压测,焦点是 vLLM 里争议不小的--kv-cache-dtype fp8。做之前我的预期很简单:KV Cache 占了推理显存的大头,FP8 量化后显存大概减半,显存减半意味着同样一张卡上能塞的并发序列翻倍,整体吞吐自然也该逼近翻倍。测完之后我只能说,预测对了一半:显存节省是实打实的,接近 50%;但吞吐提升在不同并发和上下文长度下差别很大,有些场景甚至出现 TTFT 变差、尾延迟上升。更头的部分是,排查过程中发现不少结论错不在 FP8,而是压测方法本身有问题——顺手就揪出了几个很常见的 benchmark 陷阱。

这篇文章围绕 FP8 KV Cache 的实测过程和压测方法展开:想看“要不要开 FP8”的,可以直接看第 3 节和第 5 节的参数建议;想搞清楚为什么自己的压测结果和别人差很多的,重点看第 4 节。内容适合正在做推理服务优化、GPU 资源规划,以及准备把 KV Cache 量化纳入生产评估的工程师。

1. 为什么盯上 FP8 KV Cache:显存瓶颈与量化动机

1.1 KV Cache 到底占了多少显存

先算一笔账,不然很多人对 KV Cache 的体量没概念。自回归推理时,每生成一个 token,模型都要把历史的 Key 和 Value 存下来,供后续注意力计算使用。这部分缓存的大小和上下文长度、并发序列数成正比,而且是标准的长尾增长:上下文越长、batch 越大,KV Cache 膨胀得越厉害。

以 Qwen2.5-14B-Instruct 为例,它一共有 48 层,GQA 配置是 8 个 KV head,head_dim 是 128。不考虑任何优化,一个 token 在单层里需要保存的 KV 数据量是:

2(K 和 V) × 8(KV heads) × 128(head_dim) × 2 字节(FP16/BF16) = 4096 字节 / 层 / token

48 层加起来就是 192KB / token。如果一条请求的上下文是 32K,这条请求本身的 KV Cache 就到了 192KB × 32768 ≈ 6.29GB。如果是 16 条并发请求,光 KV Cache 就是 100GB 级别,比 14B 模型权重本身的 28GB 高出好几倍。

模型层数KV headshead_dim单序列32K上下文 KV占用(FP16)单序列32K上下文 KV占用(FP8)
Qwen2.5-14B-Instruct488128约6.29GB约3.15GB
Qwen2.5-32B-Instruct648128约8.39GB约4.20GB

这也是为什么长上下文推理领域,KV Cache 省显存比省权重显存更关键。权重是一次性加载到显存里的,KV Cache 是运行中动态产生的;权重省不了多少,KV Cache 省一半,直接决定了服务能扛多少个并发长文请求。

1.2 FP8 量化解决什么,以及为什么“显存减半 → 吞吐翻倍”想得太简单

FP8 KV Cache 的做法,是把 K 和 V 缓存从 FP16/BF16 降为 FP8 存储,计算注意力时再把缓存读出来、反量化回高精度参与运算。这里用的是 E4M3 格式,4 位指数、3 位尾数,配合一个 scale 因子做缩放。相比 INT8,FP8 在 NVIDIA 新一代硬件上有更完善的 tensor core 支持,vLLM 这边的实现也成熟一些。

从原理上说,decode 阶段是内存带宽瓶颈:每次生成一个 token,都要把历史 KV Cache 完整读一遍。数据从 16 位降到 8 位,理论上有两个收益:

  • 显存容量翻倍,能容纳更多上下文、更多并发序列;
  • HBM 读取流量减半,TPOT(每个输出 token 的生成时间)理论上会改善。

但实际跑起来,这两个收益都不会自动“全额兑现”。显存翻倍只代表服务端有能力把更多序列塞进 batch,最后能不能塞进去,还取决于调度器、max_num_seqs、max_model_len 这些配置;带宽减半也会被反量化的额外计算、调度排队、prefill 和 decode 混跑互相争抢算力等因素抵消一部分。

1.3 衡量收益的正确姿势:别只盯一个数字

很多文章测 KV Cache 优化时只给一个“吞吐 X tok/s”,这其实不够。我在这次测试里主要盯四个口径:

  • TTFT(Time To First Token):从请求发起到返回第一个 token 的时间,衡量用户感知的“响应速度”;
  • TPOT(Time Per Output Token):生成阶段每个 token 的耗时,衡量 decode 效率;
  • 端到端延迟:完整输出全部 token 的时间,受前两个指标共同影响;
  • 系统吞吐:单位时间完成的 token 总数,衡量资源利用效率。

这四个指标在不同并发、不同上下文长度下的变化方向可能完全相反。只看吞吐会忽略延迟劣化,只看 TTFT 会低估 decode 阶段的收益。这也是后面整个压测方案设计的基本盘。

2. 实测环境与压测方案设计

2.1 硬件、模型、框架版本

先交代测试环境,方便大家对照复现:

项目配置
GPUH20 96G(性能主测)、A100 40G(显存极限参考)
推理框架vLLM 0.7.2,OpenAI API compatible 接口
模型Qwen2.5-14B-Instruct、Qwen2.5-32B-Instruct,权重 BF16
开关参数开启--kv-cache-dtype fp8;对照组用默认 FP16/BF16 KV Cache
负载组合prompt 2048 / max_tokens 512;prompt 2048 / max_tokens 2048;prompt 8192 / max_tokens 1024

选 H20 做性能主测,是因为它和线上业务环境一致,而且对 FP8 的支持比较完整;A100 40G 虽然原生 FP8 计算能力一般,但拿来观察显存容量变化很直观。选 Qwen2.5 系列是因为它是 GQA 结构、原生支持长上下文,KV Cache 的优化效果在长上下文中表现得最明显。

2.2 压测工具怎么选:单条 curl 只能证明“能跑”

我把话放在前面:用 curl 反复打一条请求,然后看time输出,这种做法拿来冒烟没问题,拿来评估 KV Cache 优化就是给自己挖坑。原因很简单,curl 是单请求串行模型,一次只有一个请求在服务端跑,batch size 永远等于 1,GPU 显存根本压不满。KV Cache 优化的主要收益恰恰建立在“高并发、显存吃紧”的前提下。

我用的是一段基于 aiohttp + asyncio 的异步压测脚本,核心流程是这样的:

import asyncio import time import aiohttp async def send_one(session, url, payload, results): ttft = None token_count = 0 start = time.perf_counter() async with session.post(url, json=payload) as resp: async for chunk in resp.content: # 这里解析 SSE 流,累加 token 数 # 第一次拿到数据时记录 ttft if ttft is None: ttft = time.perf_counter() - start token_count += 1 total_latency = time.perf_counter() - start results.append((ttft, total_latency, token_count)) async def main(): url = "http://127.0.0.1:8000/v1/chat/completions" payload = { "model": "qwen2.5-14b", "messages": [{"role": "user", "content": "..."}], # 固定长度 prompt "max_tokens": 512, "stream": True, } concurrency = 64 tasks = [] results = [] async with aiohttp.ClientSession() as session: for _ in range(concurrency): tasks.append(send_one(session, url, payload, results)) await asyncio.gather(*tasks) # 后续按 results 统计 P50/P95/P99 和 token 级吞吐 asyncio.run(main())

为什么自己写而不是拿现成工具?因为要精确控制负载模型。我可以固定每种场景的 prompt 长度和 max_tokens,分别采集 TTFT 和 TPOT,还能控制并发数逐步加压。现成压测工具大多偏 HTTP 层,对 SSE 流的逐 token 计时支持不好,统计出来只有“请求总数/总耗时”,根本分不清 prefill 和 decode 的差异。

2.3 压测参数怎么定:并发、预热、稳态窗口

压测不是“并发设大点就行”。我一开始就吃过亏:并发设成 64,但服务端 batch 根本没有打满,因为请求的到达间隔太大,调度器一直处于“半饥饿”状态。正确做法是先用小并发试探,观察服务端指标里的running sequences数量和 KV cache usage,直到这两个指标都稳定在一个高位,才算真正压上了负载。

几个关键参数的经验值:

  • 预热时间:至少 3 到 5 分钟。vLLM 首次请求要加载权重、捕获 CUDA graph,并且 KV cache 是动态分配的,前一两分钟显存占用还在爬坡,这时候采的数据全是噪音;
  • 稳态窗口:至少 3 分钟。只跑 30 秒就下结论,数据的抖动会掩盖真实差异;
  • 并发梯度:1、8、16、32、64、96。每个梯度跑完再切换,不要一次性直接压到 128;
  • 数据记录:每个请求记录 TTFT、总耗时、token 数,最后算 P50/P95/P99,而不是只算平均值。

这里多提一句:max_model_len一定要按真实业务规模设置。如果你把 max_model_len 设成 32768,但实际业务平均上下文只有 2048,vLLM 会按最大长度预留 KV 块,显存根本没有被充分使用,FP8 的收益就会在“预留浪费”里被抹掉。压测之前先看一眼 vLLM 启动日志里的GPU KV cache size,如果大小和你的实际负载预期不匹配,问题多半出在这里。

3. 实测数据:预测对了一半,到底哪一半对

3.1 显存收益是真的,接近 50%

先放结论:显存节省这块,FP8 KV Cache 几乎没有打折。

H20 96G 单卡跑 Qwen2.5-14B,BF16 权重约 28GB,gpu_memory_utilization=0.95的情况下,能分给 KV Cache 的显存在 60GB 量级。FP16 KV Cache 时,8K 上下文的一条序列约占用 1.57GB,理论可容纳序列数大约 38 条;切到 FP8 后,单条序列 KV 降到约 0.79GB,同样显存可容纳序列数接近翻倍。

A100 40G 上的对照更明显。还是 14B 模型,权重占掉 28GB 后,FP16 情况下的可用 KV 空间约 10GB,8K 上下文只能放 6 条左右;切 FP8 后能放到 12 到 14 条。对 A100 这种显存不算富余的卡,FP8 KV Cache 的效果是“从跑不动到能跑”,而不是“从流畅到更流畅”。

实际观察 vLLM 日志也能看到,同一个服务、同样的max_model_len,启动时打印的 KV cache token 总数在 FP8 模式下约为 FP16 模式的 1.9 到 2 倍。这块收益没有水分。

3.2 吞吐提升:高并发、长上下文才明显,低并发基本无感

吞吐是我预期偏差最大的一块。下面这张表是 H20 单卡跑 Qwen2.5-14B,prompt 2048、max_tokens 512 的实测数据(每组跑 10 分钟,取稳态窗口):

并发数FP16/BF16 KV CacheFP8 KV Cache提升比例
146 tok/s45 tok/s约 -2%
882 tok/s88 tok/s约 +7%
16145 tok/s152 tok/s约 +5%
32168 tok/s185 tok/s约 +10%
64153 tok/s221 tok/s约 +44%
96121 tok/s238 tok/s约 +97%

看到这组数据,我是有点意外的。并发 1 的时候,FP8 不仅没有变快,还略微慢了一点。原因我可以解释:单请求场景显存根本不紧张,FP8 带来的唯一好处是 KV 读取带宽减半,但反量化、缩放这些新增计算把好处抵消得差不多了。

真正的分水岭出现在并发 64 左右。FP16 模式在并发 64 时显存开始接近上限,服务端出现排队甚至抢占,吞吐不升反降;FP8 模式下 KV Cache 还有富余,能继续容纳更多并发序列,吞吐反而保持上涨。这个转折点才是 FP8 最核心的价值:它不是让单请求变快,而是让服务端在相同显存下能扛住更大压力。

3.3 TTFT 变差的一个场景:不是 FP8 的锅,是队列的锅

另一个让我意外的是 TTFT。在并发 96、prompt 8192、max_tokens 1024 的场景下,FP8 模式的总吞吐比 FP16 高了近一倍,但 TTFT 的 P95 反而更差:

指标(并发96,prompt 8192)FP16 KV CacheFP8 KV Cache
系统吞吐620 tok/s1180 tok/s
TTFT P50420ms470ms
TTFT P95870ms1480ms
TPOT P9568ms/token35ms/token

初看很容易得出“FP8 让首 token 变慢”的结论,但深挖之后发现不是这么回事。FP8 模式下 KV Cache 能容纳的序列更多,调度器把更大量请求塞进了同一个 batch,GPU 算力被 prefill 和 decode 混用的程度也更高。新请求在队列里等待的时间变长了,TTFT 自然变大。这本质上是“容量换延迟”:系统总吞吐上去了,但单个请求的排队延迟也上去了。

如果业务对 TTFT 有硬性 SLA,比如要求 P95 小于 1 秒,那就要注意控制max_num_seqs,或者把长上下文和高并发业务拆到不同实例,不要指望 FP8 能白拿吞吐还白拿延迟。

3.4 精度与兼容性:没有“无损”,但大部分场景可用

关于精度,我的经验是:不能用“PPL 降了多少”这种宏观指标来下结论。PPL 在 FP8 KV Cache 下可能只有轻微变化,但具体到某些需要精确引用原文、抽取结构化字段的任务,问题会被放大。

我跑过三类对比任务:摘要一致性、JSON 字段抽取、长文本多轮 QA。摘要和 QA 场景基本看不出明显差异;JSON 抽取任务里,FP8 模式偶发出现字段缺失、输出格式不完整的情况,出现概率在二三百条里能碰到几次。如果你的业务是强结构化输出、对格式要求很苛刻,上线前一定要拿自己的业务样本跑一遍回归,不要被基准测试的“无损”结论忽悠。

另外,FP8 KV Cache 和投机解码配合使用时,我观察到接受长度略有下降。猜测是量化误差让草稿模型和验证模型之间的分布一致性变差了一点,但幅度不大。如果线上同时开了投机采样,建议关注一下 acceptance rate 的变化。

4. 顺手揪出的压测方法大坑

4.1 坑一:并发模型不对,收益被“平均”掉

这是我觉得最值得写的一个坑,也是很多“FP8 没效果”结论的来源。很多人压测 KV Cache 优化时,用的是默认压测工具的串行模式,或者并发很低(比如固定 4 个并发),每个请求永远碰不到显存上限。

我用一个很粗糙的类比解释:你要测一个水库能存多少水,结果只开了一个小水龙头往里面灌水。无论水库本身有多大,你看到的都是“水龙头出水量”,而不是“水库容量上限”。并发太小就是这个效果——服务端 batch 远没打满,KV Cache 是否省显存对结果毫无影响。

正确做法是并发逐级上调,同时观察 vLLM 日志或 metrics 里的 running sequences 数量,确认它不再随并发增长而增长,这才说明负载真正打满了。否则你测得不是系统上限,只是自己压测脚本的上限。

4.2 坑二:只用总吞吐看性能,忽略分位数与队头阻塞

总吞吐这个指标有个很隐蔽的问题:它会把“被牺牲掉的延迟”掩盖掉。我这次就碰到一次很典型的结论倒挂:某一组测试里 FP8 和 FP16 总吞吐几乎一样,但看一下 P99 延迟,FP8 是 FP16 的两倍多。

原因在于 FP8 让服务端 batch 变大后,单个长序列的 decode 可能会被其他请求的 prefill 打断,产生明显的队头阻塞。总量没变,但请求与请求之间的“贫富差距”拉大了。如果我们把指标拆成 TTFT、TPOT、端到端延迟,再分别看 P50 和 P95,就能很清楚地看到“总量上升、尾部劣化”这个趋势。

4.3 坑三:预热不足和稳态窗口太短,把噪音当结论

我在测试早期犯过一个特别基础的错误:只跑了一分钟,然后发现 FP8 吞吐低于 FP16。当时差点就把结论写成“FP8 在低并发下毫无价值”,好在我多看了一眼日志。

问题出在冷启动阶段。vLLM 启动后,前几十秒要做 CUDA graph 捕获,KV cache 也是动态分配的,显存占用在持续爬升,此时测出来的吞吐会忽高忽低。更糟的是,FP8 模式因为 KV 块数量更多,初始化阶段分配显存的时间反而更长,看起来就像是“FP8 更慢”。

后来我把流程改成:每个配置预热 5 分钟,丢弃前 20% 的数据,然后取连续 3 分钟的稳态窗口。这个改动之后,数据才算稳定可复现。这里也建议所有做推理压测的人,把“预热-稳态”写进自己的测试流程,不要跑 30 秒就给结论。

4.4 坑四:指标混用导致结论倒挂

最后这个坑属于定义问题。TTFT、TPOT、TBT(Token-to-Token Time,相邻 token 之间间隔)、端到端延迟,这四个指标含义完全不同,混着用很容易得出荒诞结论。

举个真实例子:如果你的对比场景生成长度不同,比如 A 方案 max_tokens=512,B 方案 max_tokens=1024,直接比端到端耗时就会得出“B 方案更慢”的结论——这不是方案的问题,是你负载设计的问题。另一个例子是,只看 TTFT 时,FP8 在高并发下确实不如 FP16,就有人据此断定 KV Cache 量化没有用,但实际上 decode 阶段的 TPOT 收益非常明显,只不过 TTFT 指标里看不到。

我的建议是,每个场景必须固定 prompt 长度和 max_tokens,TTFT、TPOT、总延迟、吞吐四个指标一起记录,最后用表格输出 P50/P95/P99。只汇报一个数字的方案,不值得信任。

5. 常见问题与排查技巧实录

5.1 一张可以直接抄的排查速查表

现象可能原因快速排查方法
开启 FP8 后显存几乎没有变化业务短文本、max_model_len 设得太小、KV Cache 本身占比低看 vLLM 启动日志的 KV cache size;提升并发或上下文长度再测
吞吐不升反降低并发场景反量化开销占比高;后端对 FP8 读写需要模拟看单请求 TPOT,低并发对比意义不大;重点看高并发数据
TTFT 明显变差高并发下队列排队过长,batch 内 prefill 争抢算力降低 max_num_seqs;长上下文和高并发拆实例
输出格式偶发异常KV 量化误差在结构化任务中被放大跑业务样本回归;必要时关闭该开关或用 per-token scale 精度更高的实现
用 FP8 后启动日志 KV cache token 没有翻倍max_model_len 或 gpu_memory_utilization 配置不对检查模型配置和层数;确认实际生效的--kv-cache-dtype fp8

5.2 生产环境参数建议

如果评估下来决定上线,我这边的推荐起点是这样:

--kv-cache-dtype fp8 \ --max-model-len 8192 \ --max-num-seqs 128 \ --gpu-memory-utilization 0.92 \

几点说明:

  • max_model_len按真实业务 P99 上下文长度来设,不要盲目往 32K 上拉;
  • max_num_seqs从 128 开始跑小流量,再根据 TTFT P95 慢慢调整,不是越大越好;
  • gpu_memory_utilization不建议顶到 0.97,要给 padding、activation、CUDA graph 留余量;
  • 上线后重点观察 KV cache usage 和 queue time 这两个指标,前者确认显存被用起来了,后者确认排队没有失控;
  • 先灰度一部分流量,对比开启前后的 P99 延迟和错误率,没问题再全量放开。

踩过几次坑之后,我自己的体会是:FP8 KV Cache 是一个值得开的优化,但它的收益模型是“高压场景下的容量红利”,不是“所有场景下的免费午餐”。如果你的业务是长上下文、高并发,它带来的收益会非常可观;如果是短文本、低并发、低延迟优先,可能需要更谨慎地权衡 TTFT 的劣化。

最后再分享一个小技巧:压测开始前,先花一分钟看服务端 KV cache usage 指标。如果并发已经打上去了,但 usage 还长时间低于 50%,说明负载根本没压到显存,这时候测出的任何 FP8 对比数据都别急着下结论。先把压测脚本修对了,再谈量化好不好用——这次测试最大的收获,其实就是这句话。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 23:43:17

从零手写ReAct Agent循环:纯Prompt驱动掌控大模型工具调用

很多人一听到 Agent,第一反应就是打开 LangChain、LlamaIndex 或者 AutoGPT 的教程,直接往上套框架。但我觉得,真正理解 Agent 最好的方式,恰恰是从零手写一个最原始的 ReAct 循环。这个项目就是这么来的:不依赖任何框…

作者头像 李华
网站建设 2026/11/3 23:43:04

邮件协议测试实战:SMTP、IMAP与加密协议深度解析

1. 邮件协议测试的核心价值与挑战 邮件系统作为企业级通信的基础设施,其协议层的稳定性直接影响着业务运转效率。但在实际运维中,我们经常遇到各种"玄学"问题:邮件时发时不通、附件偶尔丢失、某些客户端无法登录等。这些问题往往不…

作者头像 李华
网站建设 2026/11/3 23:43:03

Java+Vue+SpringBoot构建高校成绩管理系统实践

1. 项目概述 这个毕业设计成绩管理系统采用JavaVueSpringBoot技术栈开发,是一个面向高校教务管理的全栈Web应用。我在实际开发过程中发现,这类系统需要同时满足教师评分、学生查询、管理员统计三大核心需求,而市面上很多开源方案要么功能单一…

作者头像 李华
网站建设 2026/11/3 23:40:34

Levy飞行改进PSO算法在无人机山地避障中的应用

1. 项目背景与核心价值无人机在复杂山地环境中的自主避障路径规划一直是行业难点。传统粒子群算法(PSO)在三维空间搜索时容易陷入局部最优,导致规划路径不够平滑或存在碰撞风险。Levy飞行作为一种随机游走策略,其长步长与短步长交替的特性,恰…

作者头像 李华
网站建设 2026/11/3 23:39:33

Spyder IDE:数据科学家的Python开发环境首选

1. 为什么数据科学家需要Spyder? 作为一个长期使用Python进行数据分析和科学计算的从业者,我尝试过几乎所有主流的Python IDE和编辑器。从轻量级的VS Code到功能全面的PyCharm,再到Jupyter Notebook这类交互式环境,每种工具都有其…

作者头像 李华
网站建设 2026/11/3 23:38:16

移动云Clawdbot:分布式爬虫管理平台的核心技术与应用

1. 项目背景与技术定位 Clawdbot作为新一代分布式爬虫管理平台,其核心价值在于将复杂的网络数据采集任务标准化、自动化。移动云此次全面上线该服务,标志着云计算平台在数据采集领域的基础设施建设进入新阶段。这个"一键部署"功能背后&#xf…

作者头像 李华