当前位置: 首页 > news >正文

vLLM推理服务假死排查-多模态缓存幽灵Key导致死循环

契机

生产环境中的vLLM推理服务突然假死:进程存活、显存正常占用、健康检查端点返回正常,但所有推理请求全部超时。GPU-util为0,CPU拉到100,极端环境出现,特指高并发

环境说明

  • 推理框架:vLLM-v0.23.0

  • 模型:多模态大模型

  • 驱动:Driver Version: 575.57.08

  • cuda:release 12.9, V12.9.86

  • 启动参数

    VLLM_LOGGING_LEVEL=DEBUG \ PYTHONUNBUFFERED=1 \ CUDA_VISIBLE_DEVICES=x \ nohup /home/conda/envs/vllm/bin/vllm serve /home/models/xxxx \ --served-model-name xxxx \ --host 0.0.0.0 \ --port 20100 \ --trust-remote-code \ --enable-prefix-caching \ --gpu-memory-utilization 0.9 \ --uvicorn-log-level debug \ --log-error-stack \ --enable-log-requests \ --enable-log-outputs \ --enable-request-id-headers \ --max-log-len 2000 \ --enable-logging-iteration-details \ > vllm_debug.log 2>&1 &
  • 运行时长:服务已稳定运行数天(约 100 万次迭代),突发假死

这里多说一嘴,由于我是12.9的驱动所以装vllm相当吃力,生产环境又不敢升级驱动,如果你刚好也是12.x驱动,一定要求https://vllm.ai/找安装方法,并且安装过程需要把加速源全部禁用,安装过程中观察下载连接和地址,一定不要从镜像下载!

pip install vllm --extra-index-url https://wheels.vllm.ai/0.24.0/cu129 --extra-index-url https://download.pytorch.org/whl/cu129 --index-strategy unsafe-best-match

故障现象

检测项状态备注
进程 PID存活EngineCore
GPU 显存正常正常
GPU 利用率0%异常
CPU 利用率136%EngineCore 进程,异常高
/health端点200 OK正常返回
/v1/models端点200 OK模型列表正常
/v1/chat/completions超时15s+ 无响应

诊断过程

外部观测

# 1. GPU状态nvidia-smi# GPU : 显存 xxxxMiB / 利用率 0%# 2. 进程CPU占用top-H-p 3791245# EngineCore 子进程 CPU 136%,远超正常值# 3. 健康检查curl-s http://0.0.0.0:20100/health# 返回 OK# 4. 推理请求curl-s--max-time 15 http://0.0.0.0:20100/v1/chat/completions \-H "Content-Type:application/json" \-d '{...}'# 超时,无响应服务处于假死状态——http层存活但调度引擎冻结

debug日志分析

# 5. 查找最后一次正常推理grep-a "generation tokens" debug.log|tail-20# 最后一次有实际生成吞吐:00:30:42,之后全部归零# 6. 查找EngineCore最后一条日志grep-a "EngineCore" debug.log|tail-10# 最后一条:00:30:33 "EngineCore waiting for work"# 之后 EngineCore 再无任何输出EngineCore在00:30:33之后完全静默,主循环停止运转

Metrics端点

# 7. 查看vLLM内部指标curl-s http://0.0.0.0:30000/metrics|grep-E "running|waiting|queue"# vllm:num_requests_running: 0# vllm:num_requests_waiting: 0请求已被APIServer接收(日志中可见 "Added request"),但EngineCore的调度器显示 Running:0,Waiting:0 —— 请求在到达调度器之前就消失了。

py-spy抓栈

# 8. 安装 py-spy(不依赖 ptrace,通过 /proc 读取)pip install py-spy# 9. 抓取 EngineCore 进程的 Python 调用栈py-spy dump--pid 3794291--locals#Thread-2持有GIL,在popitem中死循环。由于CPython的GIL机制,该线程阻塞了EngineCore所有其他Python线程Thread 3821499 (active+gil):"Thread-2 (process_input_sockets)"popitem (vllm/utils/cache.py:196) !!卡在这里!! __setitem__ (cachetools/__init__.py:82) __setitem__ (cachetools/__init__.py:294) get_and_update_item (vllm/multimodal/cache.py:662) get_and_update_features (vllm/multimodal/cache.py:607) preprocess_add_request (vllm/v1/engine/core.py:829) process_input_sockets (vllm/v1/engine/core.py:1528)

strace确认

# 10. strace跟踪EngineCore子进程strace-p 3794291-f-e trace=futex-c# 3 秒内仅 8 次 futex 调用(微秒级)# 其余时间全部在用户态Python代码中空转确认纯用户态 CPU 消耗,没有系统调用阻塞,是Python死循环

源码分析

# cachetools/__init__.py:82class Cache:def __setitem__(self,key,value):...while self.__currsize + size > self.maxsize:self.popitem()# ← 调用 vllm 重写的 popitem# vllm/utils/cache.py:196def popitem(self,remove_pinned=False):lru_key = next(key for key in self.order if key not in self.pinned_items) value = self.pop(lru_key)# ← pop 一个不在实际缓存中的 keyreturn (lru_key,value)# 问题:pop() 对不存在的 key 返回 None,currsize 没有减少# 回到 cachetools 的 while 循环:currsize + size > maxsize 仍为真# → 再次 popitem → 再次失败 → 死循环

刚好去搜索日志中的异常

grep-a "AssertionError\|Expected a cached" debug.log#找到报错日志(EngineCore pid=3794291) ERROR 06-29 00:28:41[v1/engine/core.py:1616]AssertionError:Expected a cached item for mm_hash='a874ce...4739d0'

结论:

  1. touch() 方法(cache.py#L120-L124)对不存在的 key 将其写入内部 LRU 顺序链表 __order,但不写入实际缓存数据 _Cache__data
  2. get_and_update_item()(cache.py#L660)在 assert 失败时,setitem未被执行,但 touch() 已写入的幽灵 key 残留在 __order 中
  3. 后续任意请求触发缓存写入 →setitem进入驱逐循环
  4. popitem() 从 __order 取出幽灵 key → pop() 找不到实际数据 → currsize 不减 → 死循环

验证复盘

# vLLM GitHub Issue #43941# 标题: "Bug: Infinite loop in EngineCore during multimodal cache eviction"# 状态: 已关闭,合入 PR #43595# 修复版本: v0.24.0

完全匹配的已知 Bug,与我们的 py-spy 堆栈、日志、时间线一致

时间线: 00:28:41 多模态缓存 AssertionError 触发 └→ touch() 已将幽灵 key 写入 __order └→ assert 失败,__setitem__ 未执行,幽灵 key 残留 00:28:41 引擎继续处理其他请求(幽灵 key 暂不影响) 00:30:33 某请求触发缓存写入 → __setitem__ 进入驱逐循环 └→ popitem() 取出幽灵 key └→ pop() 返回 None,currsize 未减少 └→ while 循环条件永真 → 死循环 └→ Thread-2 持有 GIL,EngineCore 完全冻结 00:30:33+ 所有后续请求均失败,客户端超时
位置代码问题
cache.py#L120-L124touch()的 except 分支将不存在的 key 写入__order,产生幽灵 key
cache.py#L196-L208popitem()驱逐逻辑未检查 key 是否真实存在于缓存中
cache.py#L658-L664get_and_update_item()touch()__setitem__之间非原子,异常导致不一致

修复方案

本着不升级vllm的方案,我直接去改conda环境中包的源码

touch-不存在的key不操作

# 原代码(L120-L124) def touch(self, key: _K) -> None: try: self._LRUCache__order.move_to_end(key) except KeyError: self._LRUCache__order[key] = None # ← 幽灵 key 来源 # 修复后 def touch(self, key: _K) -> None: if key in self: self._LRUCache__order.move_to_end(key)

popitem—跳过并清理幽灵key

# 原代码(L196-L208) def popitem(self, remove_pinned: bool = False): if not remove_pinned: lru_key = next( (key for key in self.order if key not in self.pinned_items), ALL_PINNED_SENTINEL, ) if lru_key is ALL_PINNED_SENTINEL: raise RuntimeError(...) else: lru_key = next(iter(self.order)) value = self.pop(cast(_K, lru_key)) return (lru_key, value) # 修复后 def popitem(self, remove_pinned: bool = False): while True: if not remove_pinned: lru_key = next( (key for key in self.order if key not in self.pinned_items), ALL_PINNED_SENTINEL, ) if lru_key is ALL_PINNED_SENTINEL: raise RuntimeError(...) else: lru_key = next(iter(self.order)) if lru_key in self: # ← 仅驱逐真实存在的数据 value = self.pop(cast(_K, lru_key)) return (lru_key, value) self._LRUCache__order.pop(lru_key, None) # ← 清理幽灵 key

两层防御:改动1从源头杜绝幽灵key产生,改动2确保即使有其他路径产生幽灵key,驱逐时也不会死循环

对应上游PR:vllm-project/vllm#43595,已合入v24

复现路径

  • vLLM v0.23.0 或更早版本
  • 使用多模态模型(视觉-语言模型),启用prefix caching
  • 在缓存接近容量上限时,发送一条触发了多模态hash不存在于缓存的请求
# 1. 启动 vLLM 服务(使用 v0.23.0)vllm serve <多模态模型路径>\--host 0.0.0.0--port 20100 \--enable-prefix-caching \--gpu-memory-utilization 0.9# 2. 发送大量多模态请求填满缓存for i in $(seq 1 500); do curl-s http://0.0.0.0:20100/v1/chat/completions \-H "Content-Type:application/json" \-d '{"model":"...","messages":[{"role":"user","content":[{"type":"text","text":"describe"},{"type":"video_url","video_url":"<video_path>"}]}]}' & done# 3. 发送触发幽灵 key 的请求# 需满足:mm_hash 在 cache 的 __order 中但不在 _Cache__data 中# 实际场景中较难精确控制,高并发下概率触发# 4. 验证假死curl-s--max-time 10 http://0.0.0.0:20100/v1/chat/completions \-H "Content-Type:application/json" \-d '{"model":"...","messages":[{"role":"user","content":"hello"}]}'# 预期:超时# 5. 验证根因py-spy dump--pid <EngineCore_pid>--locals# 预期:Thread-2 卡在 vllm/utils/cache.py popitem()

单元测试如下

# 模拟 touch() 产生的幽灵 keyfrom vllm.utils.cache import LRUCache cache = LRUCache(capacity=100,getsizeof=lambda x:10) cache["real_key"]= "real_value"# 模拟 touch() 对不存在的 key 写入 __ordercache._LRUCache__order["ghost_key"]= None# 填满缓存触发驱逐for i in range(9):cache[f"key_{i}"]= f"value_{i}"# 此时再次 setitem 会触发 popitem()# 如果 popitem 没有防御性检查,将死循环try:cache["new_key"]= "new_value"except Exception:pass# 在修复前会死循环

总结

http://www.cnnetsun.cn/news/3102055.html

相关文章:

  • 江苏公考培训市场“诸神混战”,谁在裸泳谁在真练兵?
  • 从零开始学Linux(三)
  • 板球击球手50分节点破百概率预测模型
  • 中兴光猫工厂模式破解:5分钟开启永久Telnet访问权限
  • 【webview】原生 App 与 H5 双向通信完全指南:JSBridge 原理与实战
  • Linux 【05- scp命令超详细教程】
  • Sunshine游戏串流主机:三步打造你的私人游戏云,彻底告别延迟困扰
  • Sunshine游戏串流终极指南:三步打造你的私人云游戏服务器
  • claude code 开发实践 - 生产级别的项目规范
  • 东芝TC78H653FTG与PIC18LF46K22的直流电机驱动方案
  • 科普漫画:散热器的临终独白:我不是被热死的,我是被闷死的
  • AI 生成中文海报为何频现“乱码”:文字渲染的技术瓶颈与优化路径
  • 从vNIC到物理网卡的完整链路追踪:VMware网络不通的8层协议栈穿透式排查法(含Wireshark过滤模板下载)
  • Kali Linux实战:用iptables构建动态防火墙防御SSH爆破与Ping洪水
  • 抖音无水印下载终极指南:从原理到实践的完整技术实现
  • 空洞骑士模组管理终极指南:使用Scarab轻松管理100+游戏模组
  • 交通行业健康风控新路径:手环体征监测落地动态健康管理体系
  • 抖音下载终极指南:5分钟搞定无水印视频批量下载的免费神器
  • 嵌入式系统硬件去抖动矩阵键盘设计与实现
  • 新闻编辑室AI调度系统:人机协同的动态内容轮播架构
  • TDLAS 激光气体检测设备高低温、震动可靠性测试方案与国产化硬件验证
  • 接口自动化测试进阶:从脚本到架构的用例设计思维与实践
  • PotPlayer字幕翻译插件:打破语言壁垒的智能观影解决方案
  • Classifier-Free Guidance(CFG)全面解读:从 Classifier Guidance 到现代扩散模型的核心技术
  • 使用JMeter进行LDAP认证性能压测的完整实践指南
  • 两节/三节串联锂电池保护芯片全系列方案,电路图BOM表免费公开下载
  • Linux远程连接实操:Xshell、Xftp连接Ubuntu完整教程(附报错解决方案)
  • 收藏!小白程序员必看:AI大模型时代,如何抓住职业新风口?
  • VMware Workstation/ESXi打印机映射失效全解:从USB重定向到网络打印协议栈深度剖析(含PowerShell自动化脚本)
  • BetterNCM Installer:网易云音乐插件生态的智能入口