最近在做一个基于Llama-3.1-8B的Agent,自己用PyTorch写了个简单的ReAct循环,没有用LangGraph之类的框架。就是很朴素的:模型推理→解析工具调用→执行工具→把结果拼回对话历史→再推理。
用PyTorch写Agent循环,显存越跑越大,大家有遇到吗?
全部回复
共 107 条你这个问题我太熟悉了,之前写ReAct循环也踩过一模一样的坑。八成不是模型本身泄漏,而是你的对话历史在无限膨胀,每轮把工具返回结果原封不动拼回去,context越拉越长,KV cache自然跟着涨。可以先用torch.cuda.memory_allocated()在每个阶段打点,看看是推理时涨还是拼历史时涨,一般能定位到。另外工具返回里如果带了一大坨JSON或者网页原文,一定要截断或摘要,不然几轮下来直接把显存吃干净。还有个容易忽略的点,解析工具调用时如果用了正则或者eval,可能会留下计算图引用,记得用torch.no_grad()包住推理部分。实在不行就在每轮结束后del掉中间张量再empty_cache,虽然治标不治本但能续命。你现在的历史管理是直接append还是有做窗口截断?这个决定了是改代码还是换架构。
你是不是把每轮的完整历史都重新喂进去了?我之前也踩过这个坑,序列越滚越长,KV cache跟着涨,显存自然就爆了。可以试试只保留最近几轮对话,或者给历史做个摘要压缩。另外确认下generate有没有包在no_grad里,我有个同事忘了加,梯度图一直堆着不释放,跑几十轮就OOM了。
对话历史无限增长是显存爆炸最常见的原因,每轮把工具返回结果都拼进去,序列长度很快就上千了。可以试试给历史做个滑动窗口,或者把老的工具输出截断/摘要后再塞回去。另外记得检查一下是不是有张量挂在计算图上没detach,推理时用torch.no_grad()包一下,不然中间激活全留着。我之前也踩过类似的坑,最后发现是KV cache没清干净,每轮都新建反而没复用。
我踩过一模一样的坑,后来发现是每轮都把新的KV cache和历史拼一起,等于显存直接翻倍涨。建议把past_key_values显式传进generate,别自己手动拼完整序列。另外工具返回的长文本最好截断或摘要一下,不然几轮下来prompt就爆了。还有记得torch.no_grad(),不然计算图会一直挂着不释放。
这个坑我踩过,大概率不是模型参数本身在涨,而是你的对话历史里挂着计算图没释放。ReAct循环里如果每次都把上一轮的输出直接append进history然后送进下一次forward,而中间又没有detach或者no_grad包裹,那graph就会一层层叠上去,显存不炸才怪。你可以先试一下在解析和拼接那步对tensor做detach,或者干脆推理阶段整体套torch.inference_mode。
另外还有个隐蔽的点是KV cache,如果你手动管理past_key_values但没在每轮结束后清理旧引用,Python的GC不一定能及时回收,显存看着就是只增不减。我之前用8B跑多轮工具调用,跑十几轮就OOM,后来发现是cache和中间变量互相引用卡住了。
工具执行那部分也值得看看,有些工具返回的结果如果被塞进prompt时又转成了带梯度的tensor,那也会累积。建议你拿torch.cuda.memory_summary打一下,看是reserved涨还是allocated涨,能大概区分是碎片还是真的泄漏。
顺便问下你每轮是重新encode整个history还是只喂增量,如果是前者那显存涨也算正常,但可以通过截断或者滑动窗口压一压。
拼回对话历史的时候有没有把工具返回的 tensor 也顺手存进去了?我踩过这个坑,history 里只要挂着带梯度的中间结果,显存就一路涨。建议每轮结束把 history 里的张量 detach 掉,或者干脆只存文本再重新 encode。另外 KV cache 没手动清也会累积,可以用 torch.cuda.empty_cache() 看着点。
显存只涨不降大概率是KV cache没清干净,对话历史一直往context里塞,每次前向都在旧cache上叠,不涨才怪。你可以试试每轮只保留最近几轮历史,或者把工具返回的长文本先摘要再拼回去,别原样丢进去。另外记得推理时用torch.no_grad(),不然计算图会一直挂着。