ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用curl验证vLLM与SGLang推理服务:从端口探活到接口响应全流程

用curl验证vLLM与SGLang推理服务:从端口探活到接口响应全流程 1. 先搞清楚为什么服务启动成功不等于能用在 Ubuntu 上部署完 vLLM 或 SGLang 这类大模型推理引擎日志里刷出Application startup complete或者Uvicorn running on http://0.0.0.0:8000的时候很多人就觉得事情已经结束了。但根据我自己的经历和帮别人排障的经验这恰恰是问题刚开始的那个点。日志显示服务进程活着只代表内核把端口监听起来了不代表模型真正加载完更不代表推理接口能按预期返回正确结果。等前端、后端、客户端真正拿着 HTTP 请求来调的时候各种稀奇古怪的错才冒出来Connection refused、400 Bad Request、curl: (35) SSL 报错、请求超时……每一条都得花时间查。所以我把服务验证这件事拆成三个层次来看缺一不可进程层验证确认引擎进程还活着没有因为 OOM、显存不足或者配置文件错误悄悄退出。端口层验证确认对应的 TCP 端口确实处于 LISTEN 状态并且监听的地址符合你的预期。接口层验证用真实的 HTTP 请求打过去确认/health、/v1/models、/v1/chat/completions这些关键端点能返回合法响应。三层验证里最核心、也最容易忽略的是接口层。而接口层验证里最好用的工具就是系统里几乎一定存在的curl。不需要装 Postman不需要写 Python 客户端不依赖浏览器 GUI一条命令就能把请求发出去、把响应拿回来、把耗时算出来。尤其当你只能通过 SSH 登录服务器排查问题时curl 几乎是唯一能直接跟本地推理服务对话的手段。我见过不止一个人卡在浏览器访问不通这一步然后开始怀疑网络、怀疑防火墙、怀疑系统配置。实际上只要在服务器本地执行一条curl -v http://127.0.0.1:8000/health就能立刻区分出问题到底出在服务本身还是出在网络链路上。这也是我为什么一直强调排查大模型推理服务先把 curl 用熟练比什么都管用。2. 动手前的基础准备端口、模型名和 API 姿势2.1 先确认服务到底起没起听谁说的都不如看端口准备验证之前我通常先做三件事。第一看进程列表ps aux | grep -E vllm|sglang | grep -v grep第二看显存占用nvidia-smi如果显存里有模型权重占着一般来说模型是加载进去了如果显存空闲但是日志显示启动成功那大概率模型加载环节出过问题但被忽略了。第三也是最直接的看端口监听状态ss -tlnp | grep -E 8000|30000vLLM 默认端口是8000SGLang 默认端口是30000。如果这里能看到类似LISTEN 0 4096 0.0.0.0:8000的输出才说明端口真正起来了。这里有个坑如果启动时用了--host 127.0.0.1端口只会监听在回环地址上你从另外一台机器用服务器公网 IP 去 curl哪怕中间没有任何防火墙也必然 Connection refused。所以在准备验证之前先想清楚你的服务是要给本机用还是给远端用这直接决定启动参数里--host怎么写。2.2 OpenAI 兼容接口的基本约定为什么两个引擎能用同一套 curl 命令vLLM 和 SGLang 虽然底层调度逻辑完全不同但它们对外都实现了 OpenAI 风格的 HTTP API这是当前大模型推理引擎的事实标准。所谓 OpenAI 兼容核心就几个端点端点作用GET /v1/models获取当前服务已加载的模型 ID 列表POST /v1/chat/completions多轮对话补全最常用POST /v1/completions纯文本补全不带对话格式POST /v1/embeddings向量化接口看部署场景是否需要请求体都是 JSON 格式需要带Content-Type: application/json请求头。响应的 JSON 里对话结果一般藏在choices[0].message.contentchat或choices[0].textcompletions里。这套约定意味着你只需要把 curl 里的地址和端口换一下就能快速在两个引擎之间做横向验证排查到底是引擎本身的问题还是客户端调用方式的问题。2.3 把 curl 用顺手的几个参数少走一半弯路很多人 curl 用得少每次都是网上复制命令遇到报错就懵。我列几个验证推理服务时最常用的参数建议先记住参数作用使用场景-ssilent不显示进度条和错误信息脚本里用配合-w输出固定格式-S显示错误信息和-s搭配避免出错时一片空白-vverbose打印完整请求响应头排查握手、重定向、Header 问题-H自定义请求头传Content-Type、鉴权 token-d发送请求体POST JSON 数据-N禁用缓冲验证流式输出时及时看到数据-w输出自定义统计信息测耗时、测状态码--max-time最大等待秒数防止请求挂死另外强烈建议装一个jqsudo apt install -y jq推理服务返回的 JSON 往往又长又乱直接打印在终端里根本看不清楚。管道接一个jq结构一目了然遇到 JSON 解析失败也能马上意识到是响应内容有问题而不是自己眼睛出了问题。没有 jq 的时候python3 -m json.tool也能凑合但 jq 在过滤字段、提取内容上方便得多。3. vLLM 服务验证从健康检查到一次完整对话3.1 先看健康检查和模型列表确认服务真的就绪假设你已经用类似下面的命令把 vLLM 拉起来了vllm serve Qwen/Qwen2.5-7B-Instruct \ --host 0.0.0.0 \ --port 8000第一步先打健康检查curl -s http://127.0.0.1:8000/health正常会返回一个OK状态码是 200。这一步能通说明 HTTP 层没问题、进程活着、端口通着。但注意vLLM 的/health只反映 HTTP 服务本身的状态并不完全等于模型已经推理就绪。更靠谱的做法是紧接着请求模型列表curl -s http://127.0.0.1:8000/v1/models | jq输出大概长这样{ object: list, data: [ { id: Qwen/Qwen2.5-7B-Instruct, object: model, created: 1730000000, owned_by: vllm, max_model_len: 32768 } ] }这里有两个关键信息id字段是你后续请求里必须填的model值很多人填错就是没看这里max_model_len是当前模型的上下文上限请求时max_tokens不能设置得让总长度超过它否则会报错。3.2 用 chat/completions 验证真实推理能力健康检查和模型列表都通了只能说明服务活着还不能说明模型会推理。真正要验证的是下面这个请求curl -s http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: Qwen/Qwen2.5-7B-Instruct, messages: [ {role: user, content: 用一句话解释什么是大模型推理引擎} ], max_tokens: 256, temperature: 0.7 } | jq .choices[0].message.content如果能看到一段通顺的中文回答说明整个链路已经打通请求进来、模型加载正常、前向推理成功、结果返回正确。这里有几个细节值得留意请求体里的 JSON 必须用单引号包起来因为 JSON 本身包含双引号。如果你用双引号shell 会优先解析里面的$、反引号等特殊字符导致 JSON 结构被破坏服务端直接返回 400。messages数组里可以加 system 角色也可以只有 user。验证时建议给一个需要真实推理能力的 prompt比如解释概念写一段代码做一道计算题而不是只回一个你好。这样能确认模型的前向计算是正常的而不是走了什么缓存捷径。如果返回结果里choices[0].message.content是空字符串先检查是不是max_tokens设得太小。模型还没输出完就被截断了内容是空的或者只有几个字这种情况看起来像服务坏了其实是参数设置问题。3.3 流式输出怎么验看 SSE 数据是不是真的在流很多真实应用为了首字延迟低会开启流式输出。验证流式接口的时候普通 curl 命令会被动等待服务端全部生成完才显示结果你压根看不到流的过程。需要加-N参数禁用缓冲curl -N -s http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: Qwen/Qwen2.5-7B-Instruct, messages: [{role: user, content: 从1数到10}], stream: true, max_tokens: 64 }正常你会看到一串 Server-Sent EventsSSE格式的数据每一行以data:开头内容是一段 JSON最后以data: [DONE]收尾。用-N之后只要服务端每生成一个 token 就推送一次你这边就能实时看到数据一行行冒出来如果服务没开流式或者网络栈有缓冲问题数据会憋到最后才一次性吐出来那就说明你的调用方式或者中间代理配置有需要调整的地方。3.4 vLLM 版本差异和部署参数的坑vLLM 迭代非常快不同版本的接口行为和启动参数有差异。老版本里--model直接接模型路径新版本推荐用serve子命令--served-model-name这个参数在各个版本里都存在作用是为模型起一个对外暴露的别名强烈建议在部署时设置一个简短、稳定的名字而不是默认用完整路径vllm serve /data/models/qwen25-7b-instruct \ --served-model-name qwen25-7b \ --host 0.0.0.0 \ --port 8000这样客户端只需要记qwen25-7b这个名字模型文件路径怎么变都不影响调用。另外如果你的环境是 WSL2 或者虚拟机localhost的解析偶尔会有 IPv6 优先的问题curl 访问http://localhost:8000可能比http://127.0.0.1:8000慢或者行为异常遇到怪问题先统一用127.0.0.1试一遍再说。还有vLLM 新版本的性能波动问题社区讨论不少验证服务的时候不要只看能否返回结果同样版本、同样的模型如果响应速度和你预期差距很大先看看是不是升级后某个参数默认值变了。4. SGLang 服务验证接口差异和专属检查项4.1 启动和健康检查除了 /health还有更严格的 health_generateSGLang 的启动命令长这样python -m sglang.launch_server \ --model-path Qwen/Qwen2.5-7B-Instruct \ --host 0.0.0.0 \ --port 30000启动之后SGLang 的/health端点用法和 vLLM 基本一致curl -s http://127.0.0.1:30000/health但 SGLang 还提供了一个更狠的检查端点/health_generate它不只是返回一个静态的 OK而是会真的让模型生成一小段文本来验证推理链路完整可用curl -s http://127.0.0.1:30000/health_generate这个接口在编排容器、做探针检查的时候特别有用。普通/health可能在模型还没加载完时就返回 200 了而/health_generate是真的跑一次推理通过了才说明服务进入可服务状态。我自己在写自动化部署脚本时就只用/health_generate作为就绪探针因为它的语义更严谨。4.2 原生 /generate 和 OpenAI 兼容接口怎么选SGLang 除了兼容 OpenAI 接口还有自己原生的/generate端点。原生口子的请求体更简洁适合快速冒烟测试curl -s http://127.0.0.1:30000/generate \ -H Content-Type: application/json \ -d { text: 法国的首都是, max_new_tokens: 32, temperature: 0.2 }返回的 JSON 里text字段就是模型续写的内容。这种给上文、续下文的方式在验证模型本身有没有问题时比 chat 接口更直接因为少了对话模板组装这一层出问题更容易定位。如果你的业务走的是 OpenAI 兼容协议那就用和 vLLM 一模一样的姿势打/v1/chat/completionscurl -s http://127.0.0.1:30000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: Qwen/Qwen2.5-7B-Instruct, messages: [{role: user, content: 你好请介绍一下你自己}], max_tokens: 128 } | jq .choices[0].message.contentSGLang 新版对 OpenAI 协议的兼容程度已经很高绝大部分场景下你不需要为它单独写一套客户端逻辑。但要注意如果你的 SGLang 版本比较老/v1/models返回的模型 ID 格式可能和你启动时传的--model-path不完全一致务必以实际返回为准。4.3 运维常用的几个 SGLang 专属端点SGLang 对运维场景考虑得比较周到下面几个端点在验证和日常排查时很有用GET /get_model_info返回当前加载模型的详细信息包括模型路径、上下文长度、显存占用统计等比/v1/models的信息丰富得多curl -s http://127.0.0.1:30000/get_model_info | jqGET /metricsPrometheus 格式的监控指标包括请求数、延迟分布、缓存命中率等。验证服务时可以顺便看一眼curl -s http://127.0.0.1:30000/metrics | head -30POST /flush_cache清理 KV cache。在长时间运行后缓存占用异常、响应变慢时可以用这个接口手动清理避免为排查问题重启整个服务curl -s http://127.0.0.1:30000/flush_cache -X POST4.4 SGLang 实操中的几个注意点第一个注意点是端口冲突。SGLang 默认 30000vLLM 默认 8000如果两个引擎同时部署在同一台机器上启动第二个前最好用ss -tlnp确认端口没被占用。第二个注意点是模型加载日志。SGLang 启动时会有大量调度器和 tokenizer 相关的输出如果在日志里看到类似resume from checkpoint的字样说明它复用了缓存的权重文件加载会快很多。第三个注意点是在显存有限的设备上比如 GPU 显存只有 8G、16G或者跑在 RK3588 这类自带 NPU 的板子上SGLang 的数据并行、张量并行参数要谨慎设置启动参数不合适会导致 CUDA OOM服务起来又立刻退回去。遇到这种情况验证之前先用nvidia-smi盯一遍显存曲线比反复起服务猜测要高效得多。5. 我用 curl 测推理服务时踩过的经典报错5.1 Connection refused先分清服务、端口、地址最有名的报错是curl: (7) Failed to connect to localhost port 8000: Connection refused排这个错我有一套固定顺序先用ps aux | grep确认进程在不在再用ss -tlnp | grep 8000确认端口有没有监听然后看监听地址是127.0.0.1还是0.0.0.0。进程在、端口在但监听在回环地址你就从本机 curl 能通、从别的机器 curl 一定不通。很多远程访问不了的诡异问题查到最后都是这一条。还有一种情况是模型还没加载完端口虽然已经监听但连接被拒绝多等几十秒再试就好。5.2 SSL unexpected eof八成是协议写错了这个报错在实测里很常见curl: (35) error:0a000126:ssl routines::unexpected eof while reading我第一次遇到时差点以为是证书问题折腾半天才发现是不小心把 HTTP 服务写成了 HTTPS 去访问。vLLM 和 SGLang 裸服务默认都是明文 HTTP如果你在 URL 里写了https://curl 会尝试和对方做 TLS 握手而对方根本不说 TLS 的语言自然就报这个错。排查方法是先用curl -v http://127.0.0.1:8000/health试一遍如果确认服务本身确实是 HTTPS比如前面挂了 Nginx 做 TLS 终结那再看证书链是否完整。curl -k可以在调试时跳过证书校验但它只是绕过问题不是解决问题生产环境不要依赖它。5.3 400 错和模型名不一致请求打过去返回 400最常见的原因是model字段和服务端实际加载的模型 ID 对不上。我自己就犯过这种错启动时用的是本地路径/data/models/qwen-7b请求里却填了Qwen/Qwen2.5-7B-Instruct结果服务端直接拒绝。解决办法很简单先请求/v1/models拿到真实的模型 ID再原样填回去。部署时如果想避免这种混乱就统一用--served-model-name固定一个对外别名并且把这个别名写进接口文档里。5.4 JSON 转义、编码和超时问题排查 400 还有一个容易被忽略的点请求体里的 JSON 格式不对。零散的日志、手敲的命令很容易出现缺少引号、多余逗号、大括号不配对的情况。我现在的习惯是命令里 JSON 稍微长一点就用jq -n动态构造避免手写转义PAYLOAD$(jq -n \ --arg model qwen25-7b \ --arg content 你好请回复OK \ {model: $model, messages: [{role: user, content: $content}], max_tokens: 64}) curl -s http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d $PAYLOAD另外还有两类问题在团队协作时经常遇到一是终端编码不是 UTF-8中文请求或响应变成乱码看起来像服务坏了实际是终端显示问题二是请求长时间没有响应模型如果正在处理很长的上下文prefill 阶段耗时很久容易让人误判为卡死。这时候用--max-time 120控制超时上限并配合服务端日志确认它到底是在推理还是在等待。6. 进阶玩法性能摸底和自动化巡检6.1 用 curl 的 -w 参数量化服务响应验证不止于通不通还要回答快不快。curl 的-w参数可以输出详细的耗时统计curl -s -o /dev/null \ -w HTTP状态码: %{http_code}\n总耗时: %{time_total}s\n首字节时间: %{time_starttransfer}s\n下载量: %{size_download} bytes\n \ http://127.0.0.1:8000/health要更精确地估算首 token 延迟可以配合流式接口并观察time_starttransfer它表示从请求发出到收到第一个字节的时间流式场景下和 TTFTTime To First Token非常接近。当然curl 的时间统计只能做粗粒度冒烟测试不能替代完整压测工具但做日常巡检和版本对比已经足够。6.2 简单的并发烟雾测试想知道服务能不能扛住并发不用急着上压测工具用xargs加 curl 就能快速摸个底seq 1 10 | xargs -P 5 -I {} curl -s -o /dev/null \ -w 请求{} 状态码:%{http_code} 耗时:%{time_total}s\n \ -H Content-Type: application/json \ -d {model: qwen25-7b, messages: [{role: user, content: 回复OK}], max_tokens: 8} \ http://127.0.0.1:8000/v1/chat/completionsseq 1 10代表发起 10 个请求-P 5表示同时保持 5 个并发。看结果里有没有超时、有没有 5xx就能初步判断服务的并发能力。这个技巧在验证 SGLang 和 vLLM 时通用只是把地址端口换一下。6.3 写一个一键验证脚本这些操作步骤固定的验证流程完全可以沉淀成一个脚本。我常用的一个最小脚本是这样#!/usr/bin/env bash # 用法: ./verify_llm.sh [host] [port] [model] HOST${1:-127.0.0.1} PORT${2:-8000} MODEL${3:-qwen25-7b} BASEhttp://${HOST}:${PORT} echo 1. 健康检查: $BASE/health curl -s --max-time 10 $BASE/health echo echo 2. 获取模型列表 curl -s --max-time 10 $BASE/v1/models | jq -r .data[].id echo 3. 对话补全测试 PAYLOAD$(jq -n \ --arg model $MODEL \ {model: $model, messages: [{role: user, content: 只回复OK两个字}], max_tokens: 16}) curl -s --max-time 60 $BASE/v1/chat/completions \ -H Content-Type: application/json \ -d $PAYLOAD | jq -r .choices[0].message.content // 未获取到内容 echo 4. 基础耗时统计 curl -s -o /dev/null --max-time 60 $BASE/v1/chat/completions \ -H Content-Type: application/json \ -d $PAYLOAD \ -w 状态码: %{http_code}, 总耗时: %{time_total}s\n脚本里每一步都用--max-time兜底避免某个请求挂住导致整个巡检卡死模型名通过jq -n --arg注入天然避免 JSON 转义问题。你把这段保存为verify_llm.sh在 Ubuntu 上chmod x之后换机器、换引擎、换端口只需要改参数不需要改逻辑。最后补一句我自己的使用习惯每次部署完引擎我不会只跑一遍健康检查就结束而是固定跑一遍健康检查、模型列表、真实对话、耗时统计这个四件套然后把输出存到日志文件里。下次同事说服务好像挂了的时候翻出上次的基线数据一对比到底是参数被改过、网络链路变了还是引擎本身的性能衰减一眼就能看出来。这套 curl 验证习惯看着基础实际排障效率比什么监控面板都管用。
返回列表