优化推理服务启动命令
在智算云平台上部署模型服务(如 vLLM, TGI, Triton)时,许多用户会遇到几大挑战:
- 容器秒退:服务启动后立即显示异常,服务还没跑起来容器就销毁了。
- 排障困难:配置了错误的模型路径或显存参数(OOM),导致服务不可用,但看不到报错日志。
- 停机有损:服务更新或缩容时,因为服务没有正确接收到停止信号,导致正在推理的请求被暴力切断。
核心原则
在容器化环境中,模型推理服务的启动命令有以下几个核心原则:
- 进程常驻前台:容器的生命周期与 PID 1 进程绑定。如果启动命令执行完毕并退出,容器就会销毁。因此,服务进程不能被放入后台且不被等待(
wait)。 - 优雅响应信号:当平台进行扩缩容或更新时,会发送
SIGTERM信号。服务必须能捕获该信号并执行Graceful Shutdown(如完成当前推理请求),而不是被暴力杀除。 - 实时日志:禁用 Python 的输出缓冲,防止日志卡在缓冲区,导致报错时看不到最后的关键信息。
- 失败保留现场:在开发调试阶段,当服务因配置错误启动失败时,容器不应立即退出,而应保持运行状态(Sleep),允许用户进入排查。
如果您只是想让服务跑起来,并且能在控制台看到日志,直接使用方案一即可。
方案一 极简模式
这是最轻量级的写法,核心原则是 使用 exec 启动推理相关进程,保证其可正常接收信号。日志直接输出到标准输出(Stdout),由平台收集,在推理服务日志页面展示。
以 vLLM 为例,在启动命令输入框中填写:
# 1. 减少 Python 日志缓冲 (确保报错能立刻看到)
export PYTHONUNBUFFERED=1
# 2. 使用 exec 启动服务
# 原理:exec 让 vLLM 进程直接替换 Shell 成为 PID 1。
# 优势:K8s 的停止信号 (SIGTERM) 能直达 vLLM,实现优雅停机,不丢请求。
exec vllm serve \
--model /mnt/models/Qwen3-0.6B \
--port 8000 \
--host 0.0.0.0 \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.95注意
为什么加 exec?
- 不加
exec不影响推理服务正常启动和运行,只不支持优化停机。 - 不加
exec,Shell 会成为主进程。当您停止服务时,Shell 会拦截信号,导致推理进程无法接收停止指令(SIGTERM),最终被系统暴力强杀(SIGKILL),导致推理请求中断。
方案二 日志双写模式
适用于需要将日志同时输出到屏幕和文件(用于持久化保存或下载分析)。使用了高级的 IO 重定向技术,既保留了 PID 1 的信号传递能力,又实现了日志双写。
#!/bin/bash
set -e
# ========================================================
# 1. 配置全局日志管道
# ========================================================
# 技巧:将当前 Shell 的所有输出(stdout/stderr)重定向给 tee
# 效果:屏幕能看到日志,同时写入 /mnt/logs/service.log
exec > >(tee -a /mnt/logs/service.log) 2>&1
export PYTHONUNBUFFERED=1
# ========================================================
# 2. 启动服务
# ========================================================
echo "正在启动服务 (PID: $$)..."
# 依然使用 exec
# vLLM 会继承上面配置好的日志管道,同时保持 PID 1 的身份
exec vllm serve \
--model /mnt/models/Qwen3-0.6B \
--port 8000 \
--host 0.0.0.0方案三 自定义生命周期模式(Trap 信号转发)
您可以使用 trap 将信号转发给推理相关进程。在服务停止后执行一些清理工作(如上传日志、清理缓存、通知回调)。
#!/bin/bash
# 1. 定义信号处理函数 (Wrapper Pattern)
cleanup() {
echo "收到停止信号,正在通知 vLLM 退出..."
# 转发信号给后台的 vLLM 进程
if [ -n "$CHILD_PID" ]; then
kill -TERM "$CHILD_PID"
wait "$CHILD_PID" # 等待 vLLM 优雅结束
fi
# === 在这里执行您的后置清理逻辑 ===
echo "正在上传日志..."
# aws s3 cp ...
echo "清理完成,容器退出。"
exit 0
}
# 2. 注册信号捕获器
trap cleanup SIGTERM SIGINT
# 3. 启动服务 (必须放入后台 &)
export PYTHONUNBUFFERED=1
echo "启动 vLLM..."
vllm serve \
--model /mnt/models/Qwen3-0.6B \
--port 8000 \
--host 0.0.0.0 &
# 4. 获取 PID 并挂起脚本
CHILD_PID=$!
wait "$CHILD_PID"注意
- 因为
vllm &是后台运行,如果没有 wait,脚本会继续执行到最后一行并立即退出,导致 PID 1 结束,容器会被 K8s 瞬间销毁(秒退)。wait还确保 Shell 能被信号立即中断(唤醒)从而执行trap转发逻辑。 - 如果将启动逻辑写入脚本文件(如 bash
run-vllm.sh),请自行在run-vllm.sh内实现信号转发,实现优雅停机。
方案四:排错调试模式
在开发调试阶段,如果服务启动秒退(如 OOM、路径错误),导致容器反复重启,无法查看日志。建议出错后强制挂起容器,给您留出时间进入 Web Terminal 排查。
export PYTHONUNBUFFERED=1
# 尝试启动服务
# 逻辑:如果 vLLM 成功启动,就一直运行;
# 如果 vLLM 失败退出 (||),则打印错误并 sleep 1 小时。
vllm serve \
--model /mnt/models/Qwen3-0.6B \
--port 8000 \
--host 0.0.0.0 \
> >(tee /mnt/logs/debug.log) 2>&1 \
|| { echo "服务启动失败!系统保留现场 1 小时..."; sleep 3600; exit 1; }注意
在保留现场的情况下,如果服务异常,容器状态依然是运行中,可以进入推理服务详情页,找到「实例信息」,点击登录进入容器 Web Terminal。
常见问题
在编写推理服务启动命令时,容易在启动命令中犯以下错误。这些错误通常会导致:
- 服务无法保持前台运行(容器立即退出)
- 调试时容器瞬间退出,无法排查 OOM/路径错误等问题
- 推理进程无法接收 SIGTERM,导致无法优雅停机
下面列出最常见的错误模式和正确写法。
问题一 推理服务跑在后台,导致容器立即退出
vllm serve /mnt/models/Qwen3-0.6B &
echo "started"& 让服务进入后台,shell 很快执行完启动命令,导致容器退出,推理服务随即进行异常状态。
正确操作是保持推理服务在前台运行,以下示例参考了方案一 极简模式。
exec vllm serve --model /mnt/models/Qwen3-0.6B问题二 启动命令立即执行成功导致容器退出
vllm serve ... 2>&1 | tee vllm.logtee 总是执行成功,导致容器退出码立即为 0。平台发现推理服务容器退出,导致异常状态。
正确写法建议参考方案一 极简模式。如果需要写日志文件,可参考其他方案。
问题三 关于推理进程的退出与优雅关闭
如果推理进程在后台运行,或者您希望推理相关进程优雅关闭(例如等待正在进行的请求完成),您可能需要处理以下项目:
- 让推理相关进程在收到 SIGTERM 时(在控制台停止推理服务时)自己完成收尾工作
- 保留日志、等待 pending 请求、执行自己的 shutdown handler
可以参考 方案三 自定义生命周期模式(trap-信号转发),捕获主脚本退出事件,向推理进程发送一个可处理的信号(如 SIGTERM),等待它自然退出。