当线上智能体的调用量在短期内涨了十倍,公有 API 的限流、按 token 计费和上下文外发就会同时变成问题。把 Hy4 的权重拿回自己的 GPU 上,用 vLLM 起一个 OpenAI 兼容的服务,再配好工具调用和负载均衡,是团队常见的一条落地路径。
适用场景
适合已经用 Hy4 做过 PoC、准备把智能体正式接入内部系统的团队:调用量增长后,需要自己掌控并发上限、把工具调用链路留在内网、并且能按压测数据决定加几张卡。也适合需要给多个业务线统一提供模型出口、做统一鉴权和计费的平台方。
环境与前置条件
操作系统与驱动
- Linux 服务器,主流发行版的近期 LTS 版本均可,需支持所选 CUDA 版本,具体对应关系以 NVIDIA 与 vLLM 官方文档当前版本为准。
- 已安装可用的 GPU 驱动,
nvidia-smi能正常输出。 - 内网部署时需提前打通 PyPI 源或准备离线 wheel 包,以及模型权重的下载通道。
运行时
- Python 3.10 及以上,具体下限以 vLLM 官方安装说明为准。
- 建议用独立虚拟环境或容器,避免和机器上已有的 torch 版本打架。
- 需要
openai客户端库用于联调,prometheus_client相关能力由 vLLM 自带。
显存估算方法
不要凭感觉选卡,先算一笔账:
- 权重显存 ≈ 参数量 × 每参数字节数。BF16 约 2 字节/参数,FP8 约 1 字节,INT4 约 0.5 字节。
- AI 词典:KV Cache">KV Cache 与激活值另算,通常再留 20%~30% 余量。
- 结论:单卡放不下就上张量并行,把
--tensor-parallel-size设为卡数,且该数字应能整除模型的注意力头数。
磁盘与内存
- 磁盘:权重体积 × 2 作为下限,留出下载临时文件和日志空间。
- 内存:建议 ≥ 权重体积 × 1.5,加载权重时会有明显的 CPU 侧峰值。
- 每张卡最好预留几十 GB 本地盘放编译缓存。
权重来源
Hy4 的具体权重名称、仓库地址、量化版本和最大上下文长度,以腾讯官方模型页面与官方文档当前版本为准。下面的命令用 $MODEL_DIR 变量占位,替换成实际路径即可。
分步骤部署
第 1 步:确认硬件与驱动状态
```bash
nvidia-smi
nvidia-smi -L
```
前者应输出 GPU 型号、驱动版本和显存总量;后者列出所有可见 GPU 的 UUID。两者都能正常输出版本信息,说明驱动层就绪。
同时确认磁盘:
```bash
df -h /data
free -g
```
第 2 步:创建虚拟环境并安装 vLLM
```bash
sudo mkdir -p /opt/hy4
sudo python3 -m venv /opt/hy4/venv
source /opt/hy4/venv/bin/activate
pip install -U pip
pip install vllm
```
安装过程会连带拉取 torch 与 CUDA runtime wheel。如果机器上已有特定 CUDA 版本,按 vLLM 官方安装文档选择匹配的安装方式,不要盲目装最新版。
验证:
```bash
python -c "import vllm, torch; print(vllm.__version__, torch.cuda.is_available())"
```
输出中第二项为 True 即成功。
第 3 步:准备模型权重目录
把权重放到一个固定路径,例如 /data/models/hy4。若从模型仓库拉取:
```bash
export MODEL_DIR=/data/models/hy4
mkdir -p $MODEL_DIR
仓库名与分支以官方模型页面为准
huggingface-cli download <官方仓库名> --local-dir $MODEL_DIR
```
如果是离线拷贝,直接 rsync 整个目录过去,然后核对完整性:
```bash
ls -lh $MODEL_DIR
cat $MODEL_DIR/config.json | head -40
```
目录中应能看到 config.json、tokenizer 相关文件,以及若干 *.safetensors 分片。记录下 config.json 里的 max_position_embeddings,它是后面设置 --max-model-len 的上限依据。
第 4 步:单机起服务
先用最小参数跑通,再逐步加参数:
```bash
source /opt/hy4/venv/bin/activate
python -m vllm.entrypoints.openai.api_server \
--model /data/models/hy4 \
--served-model-name hy4 \
--host 0.0.0.0 \
--port 8000 \
--dtype auto \
--max-model-len 8192 \
--gpu-memory-utilization 0.9 \
--max-num-seqs 64
```
几个参数的含义:
--served-model-name hy4:客户端请求里model字段填这个名字,改名不影响内部逻辑,但改了之后所有调用方要同步。--max-model-len:单请求最大上下文,直接决定 KV Cache 占用,是最常需要调的一个值。--gpu-memory-utilization:vLLM 可占用的显存比例,0.85~0.92 之间是常见区间,留一点给驱动和其他进程。--max-num-seqs:同时进行的序列数上限,也就是批大小上限,直接影响吞吐和单请求延迟。
看到类似下面的输出即为启动成功:
```
INFO: Started server process [xxxx]
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: Application startup complete.
```
模型放不下一张卡时,加上张量并行:
```bash
--tensor-parallel-size 4
```
四张卡同时参与一个请求的计算,显存叠加,但卡间通信会带来额外延迟。
第 5 步:开启工具调用(Function Calling)
Agent 能不能干活,取决于服务端有没有正确解析工具调用。vLLM 侧需要两个参数配合:
```bash
--enable-auto-tool-choice \
--tool-call-parser <parser>
```
<parser> 的可选值与模型对话模板强相关,具体填哪个,以 vLLM 官方文档中与所用模型对应的说明为准。填错的表现是:模型把工具调用写进 content 的文本里,而不是返回结构化的 tool_calls。
如果模型权重自带对话模板,一般不需要额外指定;如果模板缺失或不匹配,再用 --chat-template 指向自定义模板文件。
发一个带工具的请求试试:
```bash
curl -s http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer empty" \
-d '{
"model": "hy4",
"messages": [
{"role": "user", "content": "帮我查一下杭州现在天气"}
],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的实时天气",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string", "description": "城市名"}},
"required": ["city"]
}
}
}],
"tool_choice": "auto"
}'
```
预期返回的 choices[0].message 中有一个非空的 tool_calls 数组,function.name 为 get_weather,arguments 是 JSON 字符串。这就是 Agent 侧需要解析的东西。
客户端拿到后执行真实函数,再把结果按 role: "tool" 回传:
```python
from openai import OpenAI
import json
client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="empty")
messages = [{"role": "user", "content": "帮我查一下杭州现在天气"}]
tools = [{...}] # 同上面的定义
resp = client.chat.completions.create(model="hy4", messages=messages, tools=tools)
msg = resp.choices[0].message
if msg.tool_calls:
call = msg.tool_calls[0]
args = json.loads(call.function.arguments)
result = get_weather(**args) # 你自己的函数
messages.append(msg)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result, ensure_ascii=False),
})
final = client.chat.completions.create(model="hy4", messages=messages, tools=tools)
print(final.choices[0].message.content)
```
注意 messages.append(msg) 这一步不能省,多数模型要求把带 tool_calls 的助手消息原样带回去,否则模板渲染会报错。
第 6 步:托管进程
用 systemd 保证崩溃自动拉起:
```ini
/etc/systemd/system/hy4-vllm.service
[Unit]
Description=Hy4 vLLM OpenAI Compatible API
After=network-online.target
[Service]
Type=simple
Environment="HF_HOME=/data/hf_cache"
ExecStart=/opt/hy4/venv/bin/python -m vllm.entrypoints.openai.api_server \
--model /data/models/hy4 \
--served-model-name hy4 \
--host 0.0.0.0 --port 8000 \
--max-model-len 32768 \
--gpu-memory-utilization 0.9 \
--max-num-seqs 128 \
--disable-log-requests \
--enable-auto-tool-choice \
--tool-call-parser <parser>
Restart=always
RestartSec=15
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now hy4-vllm
sudo systemctl status hy4-vllm
```
--disable-log-requests 在压测时必须加,否则每条请求都打日志,磁盘和 CPU 都会被拖垮。
第 7 步:多副本与负载均衡
单机跑满后,横向加机器是最直接的办法。每台起一份服务,前面挂 Nginx:
```nginx
upstream hy4_backend {
least_conn;
server 10.0.0.11:8000 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8000 max_fails=3 fail_timeout=30s;
keepalive 64;
}
server {
listen 80;
client_max_body_size 20m;
location /v1/ {
proxy_pass http://hy4_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_buffering off; # 流式输出必须关
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}
```
proxy_buffering off 不能漏,否则 SSE 流式响应会被 Nginx 攒着一起发,前端表现为“卡很久然后一次性出来”。
扩容判断不要看 GPU 利用率(推理场景下它经常是满的但仍在排队),要看排队长度。vLLM 通过 /metrics 暴露 Prometheus 指标,以 vllm:num_requests_waiting 持续升高作为扩容信号比较可靠。
第 8 步:压测找拐点
vLLM 自带压测工具,新版命令形如:
```bash
vllm bench serve \
--model hy4 \
--base-url http://127.0.0.1:8000 \
--dataset-name sharegpt \
--num-prompts 1000 \
--request-rate 10
```
旧版入口为 python -m vllm.entrypoints.benchmark_serving,具体子命令以 vLLM 官方文档当前版本为准。
压测要按阶梯来,不要一上来就打满:
1. --request-rate 1,记录基线 TTFT(首 token 延迟)与 TPOT(每 token 时间)。
2. 依次升到 5、10、20、40,每次记录吞吐(tokens/s)和 P99 延迟。
3. 找到 P99 延迟开始非线性上升的那个点,那就是单副本的容量拐点。
4. 用「目标峰值 QPS ÷ 单副本拐点」向上取整,得出副本数,再留 30% 冗余。
压测时同步观察:
```bash
watch -n 2 nvidia-smi
curl -s http://127.0.0.1:8000/metrics | grep -E "waiting|running|kv_cache"
```
KV Cache 使用率长期贴近 1.0、waiting 队列持续增长,说明该加副本或降 --max-model-len 了。
验证部署是否成功
1. 服务存活
```bash
curl -s http://127.0.0.1:8000/v1/models
```
返回 JSON 中 data[].id 应包含 hy4。
2. 普通对话
```bash
curl -s http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"hy4","messages":[{"role":"user","content":"用一句话解释什么是向量数据库"}],"max_tokens":128}'
```
choices[0].message.content 应是一段通顺中文。
3. 流式输出
```bash
curl -N http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"hy4","messages":[{"role":"user","content":"数到十"}],"stream":true}'
```
应逐行输出 data: {...},最后一行是 data: [DONE]。逐行出现而不是一次性涌出,才算流式正常。
4. 工具调用
用第 5 步的 curl,确认返回体里有非空 tool_calls。
5. 通过网关访问
```bash
curl -s http://<nginx地址>/v1/models
```
结果应与直连一致,说明负载均衡链路通了。
6. 冒烟压测
```bash
vllm bench serve --model hy4 --base-url http://127.0.0.1:8000 \
--dataset-name random --num-prompts 50 --request-rate 5
```
能完整跑完并输出吞吐统计,说明服务在并发下稳定。
常见报错与解决
报错 1:torch.OutOfMemoryError: CUDA out of memory
原因:权重 + KV Cache 超过可用显存,常见于 --max-model-len 或 --gpu-memory-utilization 设得过大。
解决:先降上下文长度和并发,再考虑加卡。
```bash
降低单请求上下文与并发批大小
--max-model-len 16384 --max-num-seqs 32 --gpu-memory-utilization 0.85
```
仍不够则改用张量并行:--tensor-parallel-size <卡数>。
报错 2:ValueError: The model's max sequence length (...) is longer than the maximum sequence length supported
原因:--max-model-len 超过了模型位置编码支持的上限,或超过了当前显存能承载的 KV Cache。
解决:先看 config.json 里的 max_position_embeddings,把参数调到不超过它,并按显存再往下压。
```bash
grep max_position_embeddings /data/models/hy4/config.json
```
报错 3:模型不返回 tool_calls,而是把 JSON 写在 content 里
原因:没开 --enable-auto-tool-choice,或 --tool-call-parser 与模型的对话模板不匹配。
解决:确认启动参数包含前者,并按 vLLM 官方文档中对应模型的说明换成正确的 parser 名称后重启服务。
```bash
sudo systemctl restart hy4-vllm
```
报错 4:OSError: ... No space left on device
原因:权重分片没下完,或下载缓存把磁盘写满。
解决:
```bash
df -h
du -sh /data/models/hy4
清理未完成的下载缓存后重新拉取
rm -rf /data/hf_cache/hub/models--*
```
报错 5:客户端报 Connection refused 或请求超时
原因:服务只监听了 127.0.0.1,或端口被占用,或防火墙未放行。
解决:
```bash
ss -lntp | grep 8000
sudo systemctl restart hy4-vllm
```
确认启动参数里有 --host 0.0.0.0,并在安全组 / firewalld 放行对应端口。
报错 6:OMP: Error #15: Initializing libiomp5.so, but found libiomp5.so already initialized
原因:环境里存在多份 OpenMP 运行时,常见于 conda 与系统库混用。
解决:优先用干净虚拟环境重装 vLLM;临时规避可设置环境变量。
```bash
export KMP_DUPLICATE_LIB_OK=TRUE
```
后续维护
进程与日志
- 统一用 systemd 或容器编排托管,开启
Restart=always,避免夜间 OOM 后服务长时间不可用。 - 生产环境务必加
--disable-log-requests,只保留启动日志和错误日志。 - 日志按天切割,保留 7~14 天,用
logrotate配置即可。
监控指标
接入 Prometheus 抓取 /metrics,重点盯四个:
vllm:num_requests_waiting:排队数,扩容主信号vllm:gpu_cache_usage_perc:KV Cache 占用,长期高位说明该降上下文或加卡- 首 token 延迟 P99:直接决定 Agent 的体感
- 单卡显存余量:低于 5% 时容易触发 OOM
配一条「排队数持续 5 分钟 > 阈值」的告警,比看 GPU 利用率有用得多。
备份
- 权重目录做一次
sha256sum校验并存档,之后每次从对象存储恢复都对比一次,避免静默损坏。 - 只备份配置:systemd 单元、Nginx 配置、启动参数、对话模板文件,全部进 Git 仓库。权重不进 Git。
- 保留一份离线权重副本,避免上游仓库变更导致无法复现。
升级
- 先在灰度实例上升级 vLLM,用同一套压测脚本跑一遍,对比 TTFT 与吞吐,没有回退再滚动替换。
- 生产环境固定 vLLM 版本号,不要用浮动的最新版,升级窗口内改配置要一起记录。
- 模型权重换代时,
--served-model-name建议改用新名字(例如带日期后缀),让调用方显式切换,避免新旧行为混在一起排查困难。
容量规划
把首次压测得到的「每千 token 消耗的 GPU 秒数」记下来,之后每次调用量翻倍,直接按这个系数推算需要加几张卡,比事后救火从容得多。工具调用场景还要额外留余量:一次带工具的多轮对话往往要请求模型两到三次,token 消耗远高于单轮问答。
