ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LMCache+vLLM KV Cache卸载实战:CPU与SSD性能对比及配置解析

LMCache+vLLM KV Cache卸载实战:CPU与SSD性能对比及配置解析 实战对比Lmcachevllm将KVcache卸载到CPU和SSD的性能差异附完整配置最近在做大模型推理服务优化的时候发现一个非常现实的问题GPU显存根本不够用。尤其是跑长上下文、高并发的场景显存被KV cache占掉一大半并发稍微调高一点就直接OOM。后来试了LMCache把KV cache从GPU显存卸载到CPU内存和SSD上效果确实立竿见影但CPU和SSD这两条路线的性能差异比我预想中大得多。这篇文章就把我亲手跑出来的对比数据、完整配置、以及踩过的坑一次说清楚。无论你是做推理服务部署的工程师还是在折腾本地大模型、被显存逼疯的研究员这篇都能给你一个明确的选型参考。先说结论如果你有富余的内存条优先把KV cache卸载到CPU只有内存也吃紧的时候才考虑SSD且一定要用NVMe接口的盘。SSD方案能救急但扛不住高并发和长序列延迟波动也明显。具体数据往下看。1. 为什么需要KV cache卸载显存瓶颈的本质在聊LMCache之前先把KV cache这个东西掰扯清楚。大模型推理时每次生成一个token都需要重新计算之前所有token的Key和Value向量。为了不重复计算推理框架会把历史token的K、V值缓存下来这就是KV cache。它有多吃显存以7B模型为例序列长度2048、并发8个请求时KV cache轻松占掉十几GB显存比模型权重本身还多。显存里的空间是固定的模型权重、激活值、KV cache三者互相抢地盘。权重不能动激活值不敢省最容易被牺牲的就是KV cache。所以传统做法是限制并发数或者把最大序列长度调低。但这直接牺牲了服务的吞吐能力和用户体验——并发高一点就爆显存上下文长一点就报错。LMCache的思路很直白既然显存不够就把KV cache挪到更便宜、更大的存储介质上也就是CPU内存和磁盘。它和vLLM的集成是原生级别的通过在KV cache传输路径上做hook把最不活跃的KV block换出到CPU内存或SSD要用的时候再换回来。听起来简单但实现上有几个关键设计直接影响性能缓存换入换出的调度策略、分块粒度、以及后端存储的读写效率。vLLM 0.5.0之后把LMCache作为官方推荐的KV cache卸载方案原理是给每个KV block标记一个访问热度LRU策略把冷块优先卸到CPU或磁盘。这样GPU显存只保留热块相当于给显存加了个“外置缓存”容量天花板一下子被打碎了。不过放到CPU和放到SSD完全是两个量级的事情。CPU走的是内存总线带宽可以达到几十GB/s延迟是纳秒级SSD哪怕是最好的NVMe顺序读也就3-7GB/s延迟是微秒级。所以LMCache在CPU和SSD上的表现差异本质上就是这两条硬件路径的代差。在动手之前我给自己的优化目标定了个基准在显存不变的前提下把并发数从原来的4路提到16路以上同时保证首token延迟在可接受范围内。如果你也有类似目标下面的对比过程可以直接抄作业。2. 测试环境与工具链选型解析没有靠谱的环境性能对比就是耍流氓。我这次用的是单机双卡A100 80G的服务器但其实80G显存还是不够跑长文本大并发恰恰是这种“显存看起来不小、实际还是不够”的场景最能暴露KV cache卸载的价值。2.1 软硬件配置一览硬件部分GPUNVIDIA A100 80G x 2驱动535.104.05CPUAMD EPYC 7543 32核内存512GB DDR4 3200SSD三星PM9A3 1.92T NVMe系统盘和数据盘分离网卡Mellanox ConnectX-6 100GbE软件部分Ubuntu 22.04 LTS内核5.15CUDA 12.1PyTorch 2.1.2vLLM 0.5.4LMCache集成版LMCache 0.1.4Python 3.10老实说vLLM和LMCache的版本匹配是最大的坑点。LMCache 0.1.x对应vLLM 0.5.x如果你用的是vLLM 0.6.x就要升级到LMCache 0.2.x。版本对不上vLLM启动的时候直接报AttributeError根本起不来。这个后面章节详细说。2.2 LMCache的核心配置项解读LMCache装好之后通过vLLM启动命令里的环境变量控制最关键的有三项环境变量作用可选值我的推荐LMCACHE_ENABLED总开关0/11LMCACHE_CPU_OFFLOAD_BUFFER_SIZECPU卸载缓冲池大小0-显存上限GB显存1/4LMCACHE_DISK_OFFLOAD_ENABLED磁盘卸载开关0/1视测试项而定LMCACHE_DISK_OFFLOAD_DIR磁盘缓存目录任意路径建议单独挂载NVMe分区缓冲池大小是需要解释的概念。LMCache不是把所有KV cache都搬到CPU而是在GPU显存里开一块固定大小的“中转站”临时放下被换出的KV block。这个值设太小SSD写入会频繁触发性能反而下降设太大留给模型权重的显存不够影响batch size。我测试下来设成显存容量的1/4效果最好。比如80G显存LMCACHE_CPU_OFFLOAD_BUFFER_SIZE20。还有一个隐藏参数容易被忽略LMCACHE_CHUNK_SIZEKV cache分块大小默认是16个token一块。这个值直接影响换入换出的粒度块越大传输效率越高但热度判断越粗糙块越小调度越灵活但会引入更多元数据开销。实测下来长文本场景用32更好短文本16就够了。我这个测试模型用的是32。2.3 测试方法与压测脚本设计我用的是vLLM自带的Benchmark脚本做压测这套脚本的好处是可以同时拿到TTFT首token延迟、TPOT每个输出token延迟和吞吐量指标维度比较完整。压测模型选的是Meta-Llama-3-8B-Instruct8B规模适中KV cache的显存占用在长文本场景下很典型。输入长度固定在2048输出长度定为128模拟的是RAG场景下的问答请求每轮请求都要带一段很长的上下文。压测请求数设成200条并发分别测了1、4、8、16、32五个档位。因为LMCache对高并发更友好所以低并发时性能退化不明显一旦并发上去CPU和SSD的差异就会快速拉开。3. 完整配置方案与启动命令拆解这一节直接上干货。我演示三套配置基线方案纯GPU、CPU卸载方案、SSD卸载方案。每套都是可以直接复制运行的完整命令并解释每个参数为什么这么设。3.1 基线方案纯GPU运行先看不启用LMCache的基线这是所有对比的地基也是后面判断性能损失的参照物。python -m vllm.entrypoints.openai.api_server \ --model /data/models/Meta-Llama-3-8B-Instruct \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.90 \ --max-model-len 32768 \ --max-num-seqs 16 \ --enable-prefix-caching三个参数需要说明--tensor-parallel-size 2双卡张量并行这个不用多说--gpu-memory-utilization 0.90vLLM允许使用90%的显存留10%给CUDA context和其他开销。如果这个值太高LMCache做CPU卸载的时候没有足够空间做中转所以后面配置里我调成0.75--enable-prefix-caching这里先开启因为RAG场景会有大量重复的前缀这个参数和LMCache叠加效果更好启动后看到init engine (finished with 0.00 second)就算成功了接着可以并发压测。3.2 CPU卸载方案内存当显存用这是我最推荐的生产环境方案。把KV cache卸载到CPU内存的前提是内存足够大实测下来8B模型配256G内存16并发下完全无压力。LMCACHE_ENABLED1 \ LMCACHE_CPU_OFFLOAD_BUFFER_SIZE20 \ LMCACHE_CHUNK_SIZE32 \ python -m vllm.entrypoints.openai.api_server \ --model /data/models/Meta-Llama-3-8B-Instruct \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.75 \ --max-model-len 32768 \ --max-num-seqs 32 \ --enable-prefix-caching注意这里三个关键变化第一LMCACHE_CPU_OFFLOAD_BUFFER_SIZE20也就是20GB的GPU显存作为中转池。为什么不是更大因为--gpu-memory-utilization 0.75意味着vLLM能支配60GB模型权重和激活值大概吃掉30GB剩下的30GB里有20GB做中转池再留10GB作为调度余量。第二--max-num-seqs 32并发上限直接翻倍。这就是卸载KV cache的核心收益——显存被释放出来可以容纳更多并发请求。8B模型在32并发下每请求分配到的显存依然够用。第三--gpu-memory-utilization从0.90降到0.75这是故意为之。LMCache在需要换出KV block时要先在GPU里拷贝block然后再传到CPU。这个拷贝动作需要临时显存留太少会直接报CUDA OOM。我试过0.85配合LMCache跑20轮就崩了降到0.75才稳。CPU卸载方案生效后逻辑上KV cache分成两层热块留在GPU冷块放CPU。因为内存读写带宽极高换入换出的调度开销远小于GPU显存空间的收益所以16并发以内几乎感知不到性能退化。3.3 SSD卸载方案把显存让给权重内存也不够用了怎么办上SSD。SSD方案适合显存和内存同时紧张的极端场景比如跑几十B的大模型或者需要超长上下文的场景。LMCACHE_ENABLED1 \ LMCACHE_CPU_OFFLOAD_BUFFER_SIZE5 \ LMCACHE_DISK_OFFLOAD_ENABLED1 \ LMCACHE_DISK_OFFLOAD_DIR/ssd_cache/lmcache \ LMCACHE_CHUNK_SIZE32 \ python -m vllm.entrypoints.openai.api_server \ --model /data/models/Meta-Llama-3-8B-Instruct \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.75 \ --max-model-len 32768 \ --max-num-seqs 32 \ --enable-prefix-cachingSSD方案的缓冲池只留了5GB。原因是LMCache有层级策略KV block先尝试驻留GPU放不下就落到CPU内存CPU内存再满才写SSD。所以缓冲池不需要太大5GB只是个临时接力区SSD才是真正的容量兜底。还有一个设置/ssd_cache/lmcache这个目录我特意用mkfs.ext4格式化了一块独立的NVMe分区而不是用根目录。原因是LMCache写KV cache是持续高IO如果和系统盘抢IO带宽SSD性能会被严重拖累而且KV cache文件量很大塞系统盘会影响其他服务。SSD方案还有个隐藏风险磁盘碎片。LMCache的KV block是固定大小的chunk文件频繁写入删除会产生大量碎片。我用了fstrim定时回收并且LMCache也提供了LMCACHE_MAX_DISK_SIZE限制默认是200GB建议根据你的磁盘容量和KV cache大小单独设置。选型参考如果你只是偶尔跑一次超长任务SSD方案够用如果是长期高并发的生产服务老老实实加内存。4. 性能数据对比实测结果与关键指标拆解终于到重点了。我跑了200条请求涵盖了纯GPU、CPU卸载、SSD卸载三种方案并发从1到32。下面是整理后的关键数据。4.1 首Token延迟对比TTFT是最能直观反映“用户等待感”的指标直接决定对话式AI的响应体验。并发数纯GPUmsCPU卸载msSSD卸载ms128531235843103564898342411687163985371142325127482196从数据看CPU卸载在低并发时几乎等价SSD在低并发时有轻微劣化但还能接受。但等到16并发以上SSD的TTFT直接飙到1秒以上32并发到了2.2秒这已经明显影响用户体验了。为什么差距这么大TTFT延迟由两部分组成模型权重加载和前向计算时间加上之前所有token的KV cache的加载传输时间。纯GPU方案KV cache全在显存里读取是亚微秒级CPU方案走PCIe和内存总线多几十微秒到几百微秒SSD方案要直接从NVMe盘读KV block单次读取延迟就在几百微秒量级。而且并发一高SSD读写排队延迟非线性恶化。这里有个关键细节8个并发时SSD的TTFT只有687ms好像还将就但到16并发直接跳到1142ms。因为NVMe盘的并发读写能力有个临界点一旦超出SSD内部调度器和FTL的写放大导致性能悬崖式下跌。我用的PM9A3已经是企业级盘换成消费级盘8并发就可能跌破体验线。4.2 吞吐量与每秒请求数对比吞吐量是另一个关键指标它决定了同样一台机器能服务多少用户。并发数纯GPUreq/sCPU卸载req/sSSD卸载req/s11.681.661.3545.895.724.02810.3710.186.411616.2219.679.243218.0428.458.63这组数据最能说明问题。CPU卸载在16并发时吞吐量反超纯GPU32并发时达到28.45 req/s比纯GPU的18.04提升了57.7%。SSD卸载则相当尴尬16并发时只有9.24比纯GPU还低32并发甚至降到8.63。CPU卸载反超的原因很好理解纯GPU方案到16并发时显存里的KV cache已经塞不下了vLLM只能阻塞等待吞吐量遇到天花板。CPU卸载方案把冷KV block挪到内存GPU的compute和KV cache访问任务保持饱和所以能继续增长。SSD方案吞吐量上不去瓶颈在SSD带宽。我们算一笔账8B模型2048个输入token生成128个token每个token的KV cache大小大概是204882*2层数 * 头维度 * K和V * 数据类型约2.3MB。一个请求要完整读完整个KV cache200个并发请求就是460MB的读流量。NVMe顺序读能到3GB/s但LMCache是按block随机读取的随机读性能大打折扣实测只有600-900MB/s自然扛不住。4.3 显存占用对比卸载到底省了多少性能数据背后还得看到底省了多少显存这决定你能跑多大的模型和多少并发。纯GPU方案下8B模型、32并发、2048长度上下文KV cache显存占用约38GB加上权重和激活值总共约62GB到了80G卡的边缘。CPU卸载方案同样条件下KV cache显存占用只有14GB中转池的active部分其他全部换到CPU内存。SSD方案更夸张KV cache落到CPU内存和SSD各一部分GPU里只有5GB活跃块。实际观察到的显存占用比方案KV cache显存占用显存占用率可支撑的最大并发纯GPU38GB90%16CPU卸载14GB75%64以上SSD卸载5GB60%64以上值得注意的是CPU卸载方案能支撑的并发上限比纯GPU翻了几倍显存占用率反而更低。因为KV cache像流水一样——GPU里只保留当前活跃请求的block冷却的立刻换出到内存。后续再加并发只需要增加CPU内存占用GPU里的KV cache总大小是恒定的。这就是LMCache省显存的本质它不是压缩了KV cache而是改变了KV cache的驻留位置。GPU只是KV cache的L1缓存CPU是L2SSD是L3容量逐级扩大速度逐级下降。4.4 TPOT与长尾延迟SSD的隐藏代价TTFT和吞吐量看的是整体表现TPOT看的是每个token的生成速度这是流式输出体验的关键。SSD方案在TPOT上的表现更差。并发数纯GPU TPOTms/tokenCPU卸载 TPOTms/tokenSSD卸载 TPOTms/token818.319.228.71621.622.841.53225.427.158.9SSD的TPOT到了32并发时接近59ms/token意味着用户看到的文字生成速度只有每秒17个token大概就是目视打字机的速度。而CPU卸载方案27ms/token每秒37个token流畅度还能接受。另外我测了P99长尾延迟SSD方案在32并发时高到天际有请求等了6秒多才出第一个token而CPU方案最差也就1秒出头。这里的原因是SSD的写放大和GC垃圾回收触发了随机长延迟峰值同一个NVMe盘平时响应1ms一旦碰到GC就可能跳到几百ms。性能波动对在线服务是致命的。所以如果做企业级的RAG服务或者聊天机器人SSD方案只适合作为冷备策略不适合作为主力的KV cache卸载路径。5. 实际操作中遇到的问题与排查实录性能数据看完了接下来分享实操中遇到的一堆问题。这些问题文档里不全写但遇到任何一个都会让你怀疑人生。5.1 版本不匹配导致报错我第一次装LMCache 0.2.2配vLLM 0.5.4启动服务时直接报AttributeError: module vllm.worker.model_runner has no attribute execute_model查了半天才发现LMCache 0.2.x对应vLLM 0.6.xLMCache 0.1.x对应vLLM 0.5.x两个版本互相改了很多内部接口不能乱配。后来把LMCache降到0.1.4和vLLM 0.5.4配对问题立刻消失。这里想提醒一句LMCache的版本更新很快每次升级前先去GitHub看Release Notes的兼容性说明。如果你用的是vLLM的源码安装版还要注意LMCache对vLLM的具体commit有依赖最好用官方编译好的wheel包。5.2 显存利用率和缓冲池的平衡难题我最早配CPU卸载时把LMCACHE_CPU_OFFLOAD_BUFFER_SIZE设成了显存的一半也就是40GB同时--gpu-memory-utilization还保持0.90。启动是正常跑了50个并发直接CUDA OOM。后来意识到缓冲池占的是GPU显存的实际物理资源。vLLM的gpu-memory-utilization控制总显存使用量LMCache缓冲池是其中的一部分。如果总使用量是90%缓冲池又设成40GB比重太大模型权重和激活值分配到的显存不够直接崩。我的调参经验是LMCACHE_CPU_OFFLOAD_BUFFER_SIZE不要超过gpu-memory-utilization * 总显存 * 0.33。80G卡用0.75利用率的话可用显存60G33%约20G这和我的实测推荐完全吻合。如果你要跑更大的模型比如70B模型权重本身就占了50-60G留给KV cache的显存更少。这时候宁可减少缓冲池大小也要保证权重加载缓冲池可以设为10G甚至5G让更多KV cache直接落到CPU。5.3 SSD缓存目录的IO风暴SSD方案踩过一个大坑LMCache的默认磁盘路径是~/.lmcache和系统盘在同一个分区。压测时发现整个系统IO卡死iostat显示util 100%CPU的wa指标飙到40%以上连SSH都开始卡。解决方法是给LMCache一个独立的NVMe分区。我是这样操作的mkfs.ext4 /dev/nvme1n1 mkdir -p /ssd_cache/lmcache mount /dev/nvme1n1 /ssd_cache chmod 777 /ssd_cache/lmcache然后在启动命令里指向这个目录。建议有条件的话这块盘不要做RAID直接单盘使用。因为LMCache的KV cache是顺序写随机读的模式RAID5的写惩罚反而会拖慢速度。另外SSD方案的磁盘空间管理要提前想好LMCACHE_MAX_DISK_SIZE默认200GB如果你用的是1TB的盘长期跑会写满LMCache会自动清理最旧的缓存块但清理GC过程本身也会引发性能波动。我建议根据KV cache的预期大小把上限设置为磁盘容量的1/3到1/2。5.4 多卡环境下LMCache的注意事项双卡跑tensor parallel时LMCache的KV cache卸载也按卡分别进行。也就是说每张卡各管各的KV cache卸载任务卸载到CPU内存时也是按卡分区域存放。配置上不需要额外设置什么但显存分配上要注意如果每张卡设LMCACHE_CPU_OFFLOAD_BUFFER_SIZE20两张卡一共会占用40GB的CPU内存做中转区这个内存消耗别忽略。我实测双卡环境下CPU方案的内存占用比单卡高了近一倍512G内存吃掉了80G左右不过还算充裕。如果你的服务器内存只有128G建议每张卡的缓冲池设小一点或者只对主卡启用卸载另一张卡保持全显存驻留。6. 热词场景下的LMCachevLLM实用扩展顺着热搜词里的一些典型场景补充几个LMCachevLLM组合的实战方向这些场景和我前面压测的数据是对得上的。6.1 单机多卡部署下的KV Cache卸载策略之前有一条热搜提到“vllm 单机多卡部署”就有不少人在多卡环境下配LMCache踩了坑。单机4卡甚至8卡时LMCache会按GPU实例分别维护KV cache缓冲区CPU内存的占用是线性增长的。4张80G的卡每张卡设20G缓冲池CPU内存就被吃掉80G。如果你的内存没有512G这么大建议把每卡缓冲池压到10G并把不常用的卡设成总开关关闭只保留几张主力卡做卸载。多卡环境下还有一个容易忽略的点当tensor parallel size大于2时模型的KV cache会自动切分到各卡每个block的数据量变小。LMCache的分块粒度如果不适配这个切分会降低换入换出的效率。我实测下来TP4时LMCACHE_CHUNK_SIZE设为16比32更稳定因为TP切分后的单卡KV block更小16个token一块更容易均匀分布。6.2 长上下文的KV Cache容量规划很多人用LMCache就是为了跑超长上下文。我试过把max-model-len设到128K8B模型下单个请求的KV cache就能到80MB。如果不卸载显存直接爆卸载到CPU内存后128K长度完全放得下。这时候要算清楚容量8B模型的KV cache每1K token约消耗1.15MBFP16128K就是147MB。如果目标是100路并发CPU内存需要准备至少15GB的KV cache容量加上LMCache缓冲区的元数据开销建议留20GB余量。SSD方案的话100路并发128K上下文需要2GB/s级别的读带宽NVMe勉强能扛SATA SSD就别想了。6.3 与SGLang等其他推理框架的定位差异热搜里出现了“sglang和vllm”对比这个话题和LMCache也有关联。SGLang有自己的RadixAttention缓存机制同样支持长上下文复用但它目前没有像LMCache这样成熟的显存-内存-磁盘三级缓存卸载方案。所以在处理超长上下文高并发的场景时vLLMLMCache的组合更有优势。但SGLang在调度算法上有自己的独到之处同样跑长上下文SGLang的prefill效率可能更高而vLLMLMCache的优势是生态成熟、API接口规范业界部署最广。选型的时候如果频繁出现“所有请求都带同样的长前缀”这个特征vLLMLMCache的prefix caching能给你额外惊喜如果请求之间前缀复用少SGLang的前缀树也不一定帮得上忙。6.4 低显存设备上的降级方案有些小伙伴在消费级显卡上跑大模型我也顺手验证了LMCache在小显存环境的表现。用一张RTX 4090 24G跑7B模型纯GPU方案最多4路并发启用CPU卸载后可以跑到8路启用SSD卸载能到12路。但SSD方案的TTFT真的很伤24G卡配NVMe SSD8并发时TTFT就已经超过3秒体验接近不可用。如果你只有消费级卡我的建议是优先加内存条内存便宜DDR4 32G不过几百块比换显卡和SSD划算得多。内存加到64G以上CPU卸载方案在RTX 4090上跑7B模型8并发是可以接受的。内存确实加不了的话SSD方案做离线批处理还行在线服务慎用。说到最后我个人的实际体会是LMCachevLLM是目前解决大模型推理显存瓶颈最务实的路径。CPU卸载是性能和容量的最佳平衡点SSD卸载是极端场景下的无奈之举但至少提供了一条路让你在显存和内存双重紧张时还能继续跑下去。如果你准备在生产环境试先把预算花在内存上然后再上LMCache这套组合拳实测下来是最省心且最稳的。到最后再分享一个调试小技巧启动后用nvidia-smi盯着显存看如果发现KV cache占用曲线是锯齿形上去又掉下来说明缓冲池太小卸得太频繁如果是一直满的说明缓冲池太大压缩了模型权重空间。调到锯齿逐渐平缓、波动幅度在20%以内你就算调明白了。
返回列表