最近想把ChatGLM3-6B搞到自己的小网站上当个问答助手,服务器是单卡A100 40G,本以为够用了。结果一跑起来,单用户对话还好,并发一上来(大概5-6个会话同时请求),显存直接飙到38G+,然后崩了。我试了Int4量化,效果倒是还行,但显存还是吃紧,而且生成速度慢了不少。还看到有人用vLLM和FastChat,但试了试配置有点复杂,切分模型不太明白。想问一下各位大佬,这种单卡部署小模型并发场景,是量化+流式输出靠谱,还是直接上模型切分或者框架优化?或者有什么更轻量的做法?先谢过!
部署ChatGLM3-6B到生产环境,显存总爆,求优化思路
全部回复
共 131 条说实话你这个场景我太熟了,之前用A100跑6B也踩过一模一样的坑。vLLM那套你嫌配置复杂其实真不用切分,单卡直接上PagedAttention就能把显存占用压下来不少,关键它自带continuous batching,5-6个并发跟玩一样,你试试把max-num-seqs调到8左右,吞吐量立马上去了。量化这块我建议你别光看Int4,可以试试FP8或者混合精度,速度损失比Int4小得多;不过你提到流式输出,我觉得这个思路也对,但优先级得排在框架后面,因为瓶颈主要在显存碎片和调度上。另外有个骚操作——把模型切一半放CPU,用accelerate的device_map配合offload,虽然慢点但能保底不崩。还有个疑问,你用的是不是原版transformers的generate?如果是的话强烈建议换掉,光torch.compile就能省10%-15%显存。最后提醒下,A100 40G跑6B理论余量很大,大概率是你上下文长度没限制,把max_length砍到2048试试,说不定直接解决。
说实话你这个情况我太熟了,之前我部署Baichuan2的时候也是被并发干崩过,A100 40G看着大,但6B模型跑满上下文加多路请求,显存碎片化比想象中严重得多。你试Int4方向是对的,但我觉得问题不在量化本身,而是你大概率没开KV Cache的显存复用,加上流式输出如果没配合连续批处理(continuous batching),每个会话的显存都被模型权重和中间激活值重复占着,那5-6路并发肯定爆。
我建议你别急着上模型切分,单卡搞张量并行纯属给自己找麻烦,vLLM确实配置繁琐,但它的PagedAttention对显存管理真的立竿见影,尤其你这种多会话场景,它能动态释放和复用KV块,实测同样并发下显存能省30%以上。如果嫌vLLM太重,你可以试试更轻的推理框架比如TGI或者CTranslate2,尤其CTranslate2在Int8下吞吐比原始PyTorch高不少,而且支持动态批处理。
另外你提到生成速度变慢,我猜你可能只是做了模型量化但没优化解码参数,试试把max_new_tokens限制在256以内,加上早停策略,别让模型硬生成到512甚至更长,这对并发占用影响巨大。还有个小技巧,如果对话场景对首token延迟不敏感,可以开启torch.compile或者用CUDA graph减少内核启动开销,A100上效果还挺明显。
最后我想问下,你目前是用HuggingFace的generate接口直接干的,还是已经用了FastAPI之类的封装?如果只是裸调HF,那显存管理基本是零优化,建议至少接一个带continuous batching的推理服务,哪怕自己写个简单的调度器也比裸奔强。你先试试把量化换到Int8(比Int4精度损失小),加上流式输出和显存上限限制,并发应该能撑到8-10路,再往上就真得考虑分布式了。
显存爆得这么狠大概率是pytorch缓存没释放,试试在推理循环里加torch.cuda.empty_cache(),或者直接上vLLM的continuous batching,你这并发量其实不用切模型,vLLM配个quantized模型能省不少事。另外流式输出对显存帮助不大,主要是提升用户体验,真正吃显存的是kv cache,建议把max_length调低点,比如1024,日常问答完全够用。我之前跑7B模型也遇到过类似问题,后来发现是beam search的锅,换成greedy sampling显存直接掉了4G。
40G都被干爆了,这并发量其实挺典型的,问题可能不在模型本身,而是显存碎片和KV cache没管理好。vLLM的PagedAttention就是干这个的,你花点时间把文档啃下来绝对值得,别嫌配置复杂。另外量化别只盯着Int4,试试AWQ或者GPTQ,配合vLLM的效果比单纯bitsandbytes好不少,速度损失也小。如果你不想上框架,那就得自己写请求队列,强制串行化推理,牺牲点响应时间换稳定,但5-6并发感觉还是不太够用。最后提醒下,A100的40G版本带宽和80G不一样,生成速度慢不一定是量化锅,也可能是你batch size开太小,实测一下不同并发下的吞吐再定方案。
我之前也踩过类似的坑,单卡A100跑6B看着余量挺大,但并发一上来显存碎片化特别严重,38G基本就是被多路请求的KV cache撑爆的。量化确实能降显存,但Int4那个速度损失在并发场景下会被放大,体感特别明显。你提到的vLLM其实值得再花点时间试试,它的continuous batching就是专门解决这种多会话吞吐问题的,不用切分模型,单卡就能用,只是初始化时要把显存预算调低点留点余量。另外我自己的经验是,别把问题全压在推理框架上,应用层做排队+流式输出,限制最大并发数(比如设成4),配合vLLM的PagedAttention,基本能稳住。还有个偏方是开torch.compile,虽然编译慢点,但显存占用能再降10%左右。如果对话历史长,记得截断或者用向量检索压缩上下文,不然KV cache涨得吓人。最后想问你一下,你说的爆显存是OOM报错还是只是接近上限?如果是前者,检查下是不是有请求没释放显存,我遇到过因为生成没结束就超时的bug导致显存泄漏。
说实话你这情况我上周刚踩过坑,单卡40G跑6B并发确实紧,但问题多半不在显存总量而在碎片化,我最后是量化到Int4加vLLM的continuous batching才稳住,吞吐直接翻倍。另外你试试把max-length限制到1024以内,流式输出配合PagedAttention能省不少,但别指望切分模型,单卡切分纯属给自己找麻烦。还有个小技巧,把输入prompt缓存一下,重复问题直接命中,显存占用能降20%。
vLLM加流式输出是正解,int4只是省显存不解决并发调度,A100跑6B瓶颈在显存带宽不在容量。
说实话你这个问题不是量化能解决的,瓶颈在并发时的KV cache和调度上。建议直接上vLLM,它自带PagedAttention,对多会话显存管理比原生HuggingFace好太多,你40G跑6B模型开8个并发问题不大。
另外别用FastChat那套切分思路了,单卡根本没必要,反而增加延迟。如果vLLM配置觉得麻烦,可以先试试把max_total_tokens调低,比如限制单会话最大生成长度到512,同时把streaming打开,显存峰值能降不少。
还有个偏门但有效的做法:用batch推理,把5-6个请求攒一起处理,配合vLLM的continuous batching,比你一个个跑省显存得多。我自己的经验是Int4+流式输出在并发下还是不如直接上vLLM来的干净,速度反而能追回来。
建议先别急着重构,你这场景大概率是显存碎片化加KV cache没管理好。可以试试把max-length限制到512或者更短,再配合continuous batching,单卡撑6个并发应该没问题。vLLM其实没那么复杂,你直接pip装完用它的OpenAI接口就行,模型切分那是多卡才需要玩的。
另外Int4慢可能是你用的GPTQ版本没做推理优化,换AWQ或者直接用bitsandbytes的NF4看看,速度差距挺大的。流式输出对显存帮助不大,主要省的是首字延迟,别指望它解决OOM。还有个野路子,如果问答场景不要求太强上下文,手动把历史对话截断到最近两轮,显存能省出小一半。
最后建议你测下峰值显存到底消耗在哪,用torch.cuda.max_memory_allocated打点,如果大部分在激活值上,那就得靠vLLM的paged attention了,这玩意专治这种病。别急着上模型切分,单卡40G切分纯属给自己找麻烦。
A100 40G跑6B模型并发5-6路就爆,大概率是KV cache没管好,不是模型权重的问题。Int4量化对权重有效,但并发时KV cache才是吃显存大头。建议试试vLLM,它的PagedAttention就是专门治这个的,配置没想象中难,照着官方example改个模型路径基本能跑。另外把max_model_len和gpu_memory_utilization调一下,别让它无限涨。
量化加流式基本够用,vLLM配好了并发确实能省不少显存,值得折腾下。