最近在做一个基于Qwen2.5的简单Agent,就是在工具调用循环里反复让模型推理、解析动作、执行工具再拼回对话历史。用的vLLM离线推理(OfflineBatch),但跑十几个回合之后显存就涨到爆,只能重启进程。我已经把history列表做了截断,只保留最近几轮,token长度也控制了。怀疑是vLLM的cache机制在长循环里没释放旧序列,或者是我对OfflineBatch的调用方式不对?有没有大佬遇到过类似情况,是应该换用HF原生pipeline还是给vLLM加什么参数?救救孩子,真不想每个Agent任务都手动清显存。
PyTorch写Agent循环时显存越用越多,是推理框架的坑还是我代码的问题?
全部回复
共 71 条这问题我踩过一模一样的坑,vLLM的cache确实会在这种循环里越积越满,特别是OfflineBatch每次调用都相当于新建一个序列,旧显存不会立刻释放。你试试在循环里显式调用一下llm.finish_requests()或者直接重建一个LLM实例,比截断history管用。另外你这场景其实不太适合vLLM,它强项是吞吐,循环里单条推理延迟敏感的话HF pipeline反而更省心,就是慢点。
遇到过,OfflineBatch默认会缓存过往seq的KV,试试enable_prefix_caching=False或者每次迭代显式llm.finish_requests()。
vLLM的prefix cache在长对话里确实容易这样,试试把max_num_seqs调小或者换连续批处理模式。
这问题我踩过,vLLM的prefix cache在Agent循环里确实不释放,试试把enable_prefix_caching关掉或者手动调下block大小。
vLLM离线推理确实会缓存旧序列,试试加--disable-cache或换HF pipeline,显存会稳很多。
我之前跑类似的agent循环也爆过显存,最后发现是vLLM的prefix cache在离线推理时没按对话轮次释放旧序列。你可以试试把每次推理的request单独提交,或者显式调一下vllm的clear_cache方法,我加了之后稳定多了。另外如果history截断做得比较狠,可能是KV cache和实际输入长度对不上导致的碎片化,建议把max_num_seqs调小一点试试。HF pipeline倒是没这问题,但速度慢不少,不太适合长循环。
vLLM的cache确实会在长循环里累积,尤其OfflineBatch每次调用如果没显式清掉之前的序列,显存就容易被旧KV cache占住。我之前也踩过这坑,后来是把每次推理的请求单独封装,调用完就调一下model.clear_cache或者重置一下LLM实例,勉强能稳住。你试试给vLLM传个max_num_batched_tokens或者把swap_space调小点,可能有点帮助。另外截断history的时候要注意,如果拼接的prompt里还带着旧的动作结果,token长度其实没真正降下来,最好确认一下实际输入长度。HF pipeline倒是没这问题,但速度慢不少,看你能接受不。
同款问题踩过坑,vLLM离线推理在长循环里确实会保留历史block的KV cache,你截断history只是管住了输入长度,但cache没跟着释放。试试给OfflineBatch传个use_v2_block_manager=False或者每次迭代手动调llm.reset(),我加了之后显存曲线稳多了。另外别急着换HF,那玩意儿慢到怀疑人生,先翻翻vLLM的--max-num-seqs和--max-model-len设置,有时候是默认值太大导致预留空间爆炸。要是还不行,就干脆每轮结束后调torch.cuda.empty_cache(),虽然治标不治本但能顶住小任务。
vLLM的gpu_memory_utilization和enable_prefix_caching这两个参数先查一下,prefix caching在长对话循环里如果命中率不高反而会攒一堆block不释放。我上次也是类似情况,后来发现是每轮都新建SamplingParams但旧的sequence对象没被GC,手动del加torch.cuda.empty_cache能缓解但不治本。你要不贴一下OfflineBatch的调用代码,看看是不是每次推理都把整个history重新encode了一遍?
我之前也踩过类似的坑,vLLM的OfflineBatch确实容易在循环里越跑越胖,尤其是你每次新建一个LLM或者SamplingParams实例的时候,它底层会挂一堆KV cache块不释放。你可以先试试把整个Agent循环包在一个LLM实例里复用,别每轮都重新初始化,这个最容易被忽略。另外vLLM有个enable_prefix_caching和gpu_memory_utilization,prefix caching在长历史重复拼接的场景下反而可能让显存涨得更快,你可以关掉对比一下。还有就是你截断history的时候注意别只截文本,工具调用的那些结构化消息如果没一起清掉,tokenizer那边算出来的长度可能跟你以为的不一样。真不行就换成HF的generate加past_key_values手动管理,虽然慢点但显存可控,至少不会跑着跑着就OOM。我现在的做法是每跑几轮就重建一次engine,土但有效,你可以先这么顶着。
vLLM的OfflineBatch确实有这个问题,它内部维护的block cache在长循环里不会主动回收,尤其你每次还拼接了新的prompt进去。我上次类似场景是把每轮推理拆成独立的LLM.generate调用,别复用同一个engine实例,显存就稳住了。你可以先试试在每轮结束后手动调一下engine里的cache清理接口,或者干脆用HF pipeline跑小规模验证是不是框架的锅。不过换pipeline性能会掉不少,取舍看你了。