macOS 15 Sequoia 跑 vLLM-Metal:文件句柄上限踩坑与稳定方案

20次阅读
没有评论

标签:macOS Sequoia、vLLM-Metal、Apple Silicon、Metal LLM、launchd、Too many open files 适用:Mac M 系列、macOS 15 Sequoia、SIP 默认开启(不建议关 SIP)

前言

最近在 macOS 15 Sequoia 上使用 vLLM-Metal 本地部署大模型,高并发压测时频繁遇到 Too many open files。 很多人会沿用旧 macOS(Sierra ~ Monterey)的老办法:写 /Library/LaunchDaemons 全局 maxfiles plist,但是这套方案在 Sonoma / Sequoia 已经彻底失效。

macOS 15 开始,在 SIP 开启状态下,Apple 在内核层面做了保护:禁止 root 通过 launchctl limit 修改用户会话全局文件句柄硬上限,常规全局配置无法突破 2048。 而 vLLM-Metal 作为本地推理服务,多并发请求、prefill、网络连接、缓存文件都会快速消耗文件描述符,很容易撞上这个限制。

先划重点:不要关闭 SIP 去绕开限制,安全代价很高;优先使用进程单独资源限制方案。

背景:macOS 的文件句柄两套体系

Linux 习惯用 /etc/security/limits.conf,但是 macOS 很早就不识别这个文件。

  • macOS 10.12 Sierra 到 Monterey:可以通过全局 launchdaemon 执行 launchctl limit maxfiles 全局调高句柄
  • macOS 14 Sonoma / 15 Sequoia(SIP 开启):sudo launchctl limit maxfiles xxx 直接报错
Could not set resource limits: 150: Operation not permitted while System Integrity Protection is engaged

两个容易混淆的内核参数,SIP 开启时同样无法修改:

sysctl kern.maxfiles
sysctl kern.maxfilesperproc

kern.maxfiles 是全系统总 vnode 池;launchctl limit maxfiles 是进程资源上限,二者互相独立,不要混为一谈。

查看当前限制的命令:

# 查看 launchd 会话限制
launchctl limit maxfiles
# 当前 shell 的 ulimit
ulimit -Sn
ulimit -Hn

方案选型(按推荐优先级)

方案一:临时调试(适合手动跑 vllm serve)

最简单,只对当前终端以及该终端启动的子进程生效,重启、新开终端、GUI 拉起的程序不继承。

ulimit -n 65536
# 再启动 vLLM-Metal
vllm serve Qwen/Qwen3.5-0.8B --gpu-memory-utilization 0.6

如果想每次打开终端自动设置,写入 zsh 配置:

echo "ulimit -n 65536" >> ~/.zshrc
source ~/.zshrc

局限:不适合后台常驻服务,通过 launchd 自动拉起的 vLLM 不会继承这个 ulimit。

方案二:LaunchAgent 单独给 vLLM-Metal 设置资源上限(推荐,SIP 无需关闭)

macOS 15 官方支持:在单个服务 plist 内部声明 SoftResourceLimits / HardResourceLimits,仅对这个进程生效,不改动系统全局策略。 适合长期后台常驻、开机自启 vLLM 推理服务。

文件路径:~/Library/LaunchAgents/com.vllm-metal.serve.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.vllm-metal.serve</string>
    <key>ProgramArguments</key>
    <array>
        <string>/opt/homebrew/bin/vllm</string>
        <string>serve</string>
        <string>Qwen/Qwen3.5-0.8B</string>
        <string>--gpu-memory-utilization</string>
        <string>0.6</string>
        <string>--distributed-executor-backend</string>
        <string>uni</string>
        <string>--host</string>
        <string>0.0.0.0</string>
        <string>--port</string>
        <string>8000</string>
    </array>
    <key>SoftResourceLimits</key>
    <dict>
        <key>NumberOfFiles</key>
        <integer>65536</integer>
    </dict>
    <key>HardResourceLimits</key>
    <dict>
        <key>NumberOfFiles</key>
        <integer>200000</integer>
    </dict>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>

加载与卸载命令:

# 设置权限
chmod 644 ~/Library/LaunchAgents/com.vllm-metal.serve.plist
# 加载服务
launchctl load ~/Library/LaunchAgents/com.vllm-metal.serve.plist
# 修改配置前先卸载
# launchctl unload ~/Library/LaunchAgents/com.vllm-metal.serve.plist

说明:

  1. ~/Library/LaunchAgents:当前用户代理,不需要 root;如果是系统级服务放到 /Library/LaunchDaemons
  2. --distributed-executor-backend uni:macOS vLLM-Metal 推荐,避开 Ray 多进程带来更多 fd、内存不稳定问题
  3. --gpu-memory-utilization M 系列统一内存建议 0.5~0.7,预留内存给系统、Metal 驱动、内核编译缓存

方案三:关闭 SIP(强烈不推荐)

关闭 SIP 之后,老式全局 com.limit.maxfiles.plist 方案才会恢复可用。 Apple Silicon 需要进入恢复模式执行 csrutil disable。 风险:系统完整性保护、系统签名校验全部失效,整机安全边界大幅下降,能不碰就不碰。

方案四:Docker / OrbStack 容器场景

容器内部 ulimit 由虚拟化层控制,不受 macOS 主机 2048 限制,在 compose 里直接配置:

services:
  llm:
    ulimits:
      nofile:
        soft: 65536
        hard: 200000

验证与测试

查看 vLLM 进程的文件句柄限制:

# 找到pid
ps aux | grep vllm
# 查看进程资源限制
launchctl procinfo <你的pid> | grep NumberOfFiles

OpenAI 兼容接口测试:

curl http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3.5-0.8B",
"messages": [{"role":"user","content":"你好"}]
}'

vLLM-Metal 在 macOS15 的额外避坑清单

  1. Python 架构必须是 arm64,Rosetta x86 Python 不支持 Metal 后端
python3 -c "import platform; print(platform.machine())"
# 输出 arm64 才正确
  1. 模型下载慢,启用 HF 镜像:
export HF_ENDPOINT=https://hf-mirror.com
  1. 并发太高依旧容易爆 fd:可以用 --max-concurrent-batches 控制推理并发,从业务侧降低文件描述符压力
  2. 老全局 plist 不要再尝试:
<!-- 这个在macOS15 SIP开启下无效,仅作历史参考 -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.limit.maxfiles</string>
    <key>ProgramArguments</key>
    <array>
        <string>launchctl</string>
        <string>limit</string>
        <string>maxfiles</string>
        <string>65536</string>
        <string>200000</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>ServiceIPC</key>
    <false/>
</dict>
</plist>

小结

macOS 15 Sequoia + SIP 开启跑 vLLM-Metal:

  1. 抛弃旧的全局 maxfiles launchdaemon 方案,内核层面已经拦截;
  2. 手动调试:先 ulimit -n 65536 再启动服务;
  3. 长期后台部署:使用带 SoftResourceLimits / HardResourceLimits 的 LaunchAgent,给 vLLM 进程单独放开文件句柄上限,是当前最稳妥、符合系统策略的方案;
  4. 优先 uni 后端,少用 Ray,降低并发压力,减少 fd 与内存异常。
正文完
可以使用微信扫码关注公众号(ID:xzluomor)
post-qrcode
 0
评论(没有评论)
验证码