32卡-64G-910B4-16后端-(Qwen3.8-27B-W8A8)集群部署报告
报告时间:2026-08-29
部署规模:4台 × 8卡 = 32卡昇腾910B4集群
一、项目概述
本项目在4台昇腾910B4 NPU服务器(共32卡)上完成 Qwen3.8-27B-W8A8 大模型的推理服务集群部署。每台服务器部署4个vLLM实例(各2卡TP),共16个后端实例。
通过机器69上的OpenResty实现反向代理、负载均衡和会话亲和力路由,对外提供统一的OpenAI兼容API服务。
部署目标
|
目标 |
状态 |
|
Qwen3.8-27B-W8A8 推理服务(16后端) |
完成 |
|
32卡NPU充分利用(4台×8卡) |
完成 |
|
外网统一API入口(xxx.xx.xxx.xxx:2219) |
完成 |
|
API密钥认证(全实例一致) |
完成 |
|
会话亲和力(X-Session-ID MD5路由) |
完成 |
|
服务崩溃/机器重启自动恢复(systemd) |
完成 |
二、硬件环境
|
项目 |
规格 |
|
服务器数量 |
4台(机器66/67/68/69) |
|
每台NPU |
8 × 昇腾910B4 |
|
集群总NPU |
32卡 |
|
单卡HBM |
64 GB |
|
集群总HBM |
2048 GB |
|
驱动版本 |
npu-smi 26.0.rc1 |
|
CPU架构 |
x86_64 |
|
操作系统 |
Ubuntu 24.04.3 LTS (noble) |
|
内核版本 |
6.8.0-124-generic |
|
机器69网卡IP |
XXX.XXX.XXX.XXX/20, XXX.XXX.XXX.XXX/24, XXX.XXX.XXX.XXX/24 |
|
集群内网网段 |
10.255.254.0/20, 10.255.11.0/24, 10.255.12.0/24 |
|
公网入口 |
xxx.xx.xxx.xxx:2219(NAT映射至机器69) |
三、软件环境
|
组件 |
版本 |
|
Docker |
29.6.1+ |
|
CANN Toolkit |
9.1.0 |
|
vLLM |
0.23.0 |
|
vLLM-Ascend |
qwen3.8-a2 镜像 |
|
torch |
2.10.0 |
|
torch_npu |
2.10.0 |
|
transformers |
5.5.4 |
|
OpenResty |
1.31.1.1 |
|
Python |
3.12.13(容器内) |
四、集群部署架构
整体架构如下,4台机器组成16后端推理集群,机器69运行OpenResty总控负载均衡器:
互联网用户
|
V
+---------------------+
| xxx.xx.xxx.xxx:2219 | <- 公网入口(NAT映射)
+---------------------+
|
V
+---------------------+
| OpenResty LB | <- 机器69 (10.255.254.69)
| (0.0.0.0:2219) | <- X-Session-ID MD5%16路由
+---------------------+
|
+-----------+-----------+-----------+-----------+
| | | | |
V V V V V
+---------+ +---------+ +---------+ +---------+
| 机器69 | | 机器68 | | 机器67 | | 机器66 |
|:8000-8003| |:8000-8003| |:8000-8003| |:8000-8003|
| NPU0-7 | | NPU0-7 | | NPU0-7 | | NPU0-7 |
| 4×TP=2 | | 4×TP=2 | | 4×TP=2 | | 4×TP=2 |
+---------+ +---------+ +---------+ +---------+
五、服务详细配置
每个实例使用2卡TP,运行在Docker容器中。启动时须映射npu-smi命令及Ascend驱动库到容器,并开启privileged模式确保设备访问权限。4台机器执行相同的启动命令(仅NPU分配和端口映射不同)。
5.1 vLLM 实例配置
docker run -d --name qwen38-tp2-0 \
--privileged \
--device /dev/davinci0 --device /dev/davinci1 \
--device /dev/davinci_manager --device /dev/devmm_svm --device /dev/hisi_hdc \
-v /usr/local/dcmi:/usr/local/dcmi:ro \
-v /data/models:/models:ro \
-v /usr/local/sbin/npu-smi:/usr/local/sbin/npu-smi:ro \
-v /usr/local/Ascend/driver:/usr/local/Ascend/driver:ro \
-p 8000:8000 \
-e ASCEND_RT_VISIBLE_DEVICES=0,1 \
quay.io/ascend/vllm-ascend:qwen3.8-a2 \
vllm serve /models/Qwen3.8-27B-W8A8 \
--served-model-name Qwen3.8-27B-W8A8 \
--api-key sk-XXXXXXXXXXXXXXXX \
--tensor-parallel-size 2 \
--max-model-len 262144 \
--max-num-seqs 32 \
--max-num-batched-tokens 4096 \
--disable-custom-all-reduce \
--distributed-executor-backend mp \
--reasoning-parser qwen3 \
--enable-auto-tool-choice \
--tool-call-parser qwen3_coder \
--speculative-config '{"method":"mtp","num_speculative_tokens":4}' \
--block-size 16 \
--trust-remote-code \
--dtype bfloat16
|
参数 |
说明 |
|
--privileged |
容器特权模式,确保NPU设备访问权限 |
|
-v /usr/local/sbin/npu-smi |
映射宿主机npu-smi命令到容器 |
|
-v /usr/local/Ascend/driver |
映射Ascend驱动库(npu-smi依赖) |
|
--served-model-name |
API返回的模型名称:Qwen3.8-27B-W8A8 |
|
--api-key |
访问密码:sk-XXXXXXXXXXXXXXXX |
|
--tensor-parallel-size 2 |
2卡张量并行 |
|
--max-model-len 262144 |
最大上下文262K tokens |
|
--max-num-seqs 32 |
最大并发序列数 |
|
--speculative-config |
MTP投机解码,num_speculative_tokens=4 |
|
--reasoning-parser qwen3 |
Qwen3推理解析器 |
|
--tool-call-parser qwen3_coder |
工具调用解析器 |
|
--dtype bfloat16 |
数据类型BF16 |
5.2 16后端实例分布
|
机器 |
实例名 |
NPU |
端口 |
内网地址 |
状态 |
|
机器69 |
qwen38-tp2-0 |
0,1 |
8000 |
10.255.254.69:8000 |
Active |
|
机器69 |
qwen38-tp2-1 |
2,3 |
8001 |
10.255.254.69:8001 |
Active |
|
机器69 |
qwen38-tp2-2 |
4,5 |
8002 |
10.255.254.69:8002 |
Active |
|
机器69 |
qwen38-tp2-3 |
6,7 |
8003 |
10.255.254.69:8003 |
Active |
|
机器68 |
qwen38-tp2-0 |
0,1 |
8000 |
10.255.254.68:8000 |
Active |
|
机器68 |
qwen38-tp2-1 |
2,3 |
8001 |
10.255.254.68:8001 |
Active |
|
机器68 |
qwen38-tp2-2 |
4,5 |
8002 |
10.255.254.68:8002 |
Active |
|
机器68 |
qwen38-tp2-3 |
6,7 |
8003 |
10.255.254.68:8003 |
Active |
|
机器67 |
qwen38-tp2-0 |
0,1 |
8000 |
10.255.254.67:8000 |
Active |
|
机器67 |
qwen38-tp2-1 |
2,3 |
8001 |
10.255.254.67:8001 |
Active |
|
机器67 |
qwen38-tp2-2 |
4,5 |
8002 |
10.255.254.67:8002 |
Active |
|
机器67 |
qwen38-tp2-3 |
6,7 |
8003 |
10.255.254.67:8003 |
Active |
|
机器66 |
qwen38-tp2-0 |
0,1 |
8000 |
10.255.254.66:8000 |
Active |
|
机器66 |
qwen38-tp2-1 |
2,3 |
8001 |
10.255.254.66:8001 |
Active |
|
机器66 |
qwen38-tp2-2 |
4,5 |
8002 |
10.255.254.66:8002 |
Active |
|
机器66 |
qwen38-tp2-3 |
6,7 |
8003 |
10.255.254.66:8003 |
Active |
六、OpenResty 反向代理配置
OpenResty运行在机器69上,监听0.0.0.0:2219。外网通过xxx.xx.xxx.xxx:2219访问,NAT映射至机器69的内网IP。
6.1 核心功能
|
功能 |
配置 |
|
负载均衡 |
16后端(4台机器各4实例) |
|
亲和力 |
X-Session-ID MD5哈希%16路由(与API认证分离) |
|
API认证 |
由vLLM后端验证 --api-key |
|
超时设置 |
connect 300s / send 3600s / read 3600s |
|
请求体限制 |
50MB |
|
CORS |
允许所有来源,暴露X-Upstream-Addr |
|
流式输出 |
proxy_buffering off |
6.2 亲和力路由设计
早期方案使用Authorization Bearer Token作为路由键,但所有用户共享同一个API Key,导致所有请求命中同一后端。最终方案引入独立的X-Session-ID请求头作为亲和力键,与API认证解耦:
路由键优先级:X-Session-ID > Authorization Bearer > remote_addr
同一Session-ID的多次请求始终路由到同一后端,保证上下文状态一致性;不同Session-ID通过MD5前8位取模均匀分布到16个后端。
排查历程:CRC32(40key全落单一后端)-> FNV-1a(相似key碰撞)-> MD5前8位(40个随机key均匀分布到16后端)。
6.3 完整 nginx.conf(16后端)
以下为生产环境最终生效的OpenResty 16后端集群配置文件:
worker_processes auto;
worker_rlimit_nofile 65536;
error_log /var/log/openresty/error.log warn;
pid /run/openresty.pid;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
include /usr/local/openresty/nginx/conf/mime.types;
default_type application/octet-stream;
log_format upstream_log '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent '
'upstream=$upstream_addr key=$sticky_key '
'rt=$request_time uct=$upstream_connect_time urt=$upstream_response_time';
access_log /var/log/openresty/access.log upstream_log;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
# === 机器69 ===
upstream backend_8000 { server 10.255.254.69:8000; keepalive 32; }
upstream backend_8001 { server 10.255.254.69:8001; keepalive 32; }
upstream backend_8002 { server 10.255.254.69:8002; keepalive 32; }
upstream backend_8003 { server 10.255.254.69:8003; keepalive 32; }
# === 机器68 ===
upstream backend_8004 { server 10.255.254.68:8000; keepalive 32; }
upstream backend_8005 { server 10.255.254.68:8001; keepalive 32; }
upstream backend_8006 { server 10.255.254.68:8002; keepalive 32; }
upstream backend_8007 { server 10.255.254.68:8003; keepalive 32; }
# === 机器67 ===
upstream backend_8008 { server 10.255.254.67:8000; keepalive 32; }
upstream backend_8009 { server 10.255.254.67:8001; keepalive 32; }
upstream backend_8010 { server 10.255.254.67:8002; keepalive 32; }
upstream backend_8011 { server 10.255.254.67:8003; keepalive 32; }
# === 机器66 ===
upstream backend_8012 { server 10.255.254.66:8000; keepalive 32; }
upstream backend_8013 { server 10.255.254.66:8001; keepalive 32; }
upstream backend_8014 { server 10.255.254.66:8002; keepalive 32; }
upstream backend_8015 { server 10.255.254.66:8003; keepalive 32; }
server {
listen 0.0.0.0:2219;
server_name _;
client_max_body_size 50m;
proxy_connect_timeout 300s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
set $sticky_key "";
set $target "";
rewrite_by_lua_block {
-- 优先级:XSessionID > Authorization Bearer > remote_addr
local session = ngx.var.http_x_session_id or ""
local auth = ngx.var.http_authorization or ""
local key = ""
if session ~= "" then
key = session
else
key = auth:match("Bearer%s+(.+)") or ""
end
if key == "" then
key = ngx.var.remote_addr or ""
end
ngx.var.sticky_key = key
-- MD5前8位十六进制转整数取模%16
local hex = ngx.md5(key):sub(1, 8)
local h = tonumber(hex, 16)
local idx = (h % 16)
local backends = {
"backend_8000", "backend_8001", "backend_8002", "backend_8003",
"backend_8004", "backend_8005", "backend_8006", "backend_8007",
"backend_8008", "backend_8009", "backend_8010", "backend_8011",
"backend_8012", "backend_8013", "backend_8014", "backend_8015",
}
ngx.var.target = backends[idx + 1]
}
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Session-ID' always;
add_header 'Access-Control-Max-Age' 1728000 always;
add_header 'Content-Type' 'text/plain; charset=utf-8' always;
add_header 'Content-Length' 0 always;
return 204;
}
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Session-ID' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range,X-Upstream-Addr' always;
proxy_pass http://$target;
add_header X-Upstream-Addr $upstream_addr always;
}
location /health {
access_log off;
add_header Content-Type application/json;
return 200 '{"status":"ok","backends":16,"machines":["69","68","67","66"]}';
}
}
}
七、集群验证结果
7.1 16后端健康检查
2026-08-29全面健康检查结果(通过机器69检查全部16个后端):
|
后端 |
地址 |
HTTP状态 |
模型名 |
|
机器69-0 |
10.255.254.69:8000 |
200 |
Qwen3.8-27B-W8A8 |
|
机器69-1 |
10.255.254.69:8001 |
200 |
Qwen3.8-27B-W8A8 |
|
机器69-2 |
10.255.254.69:8002 |
200 |
Qwen3.8-27B-W8A8 |
|
机器69-3 |
10.255.254.69:8003 |
200 |
Qwen3.8-27B-W8A8 |
|
机器68-0 |
10.255.254.68:8000 |
200 |
Qwen3.8-27B-W8A8 |
|
机器68-1 |
10.255.254.68:8001 |
200 |
Qwen3.8-27B-W8A8 |
|
机器68-2 |
10.255.254.68:8002 |
200 |
Qwen3.8-27B-W8A8 |
|
机器68-3 |
10.255.254.68:8003 |
200 |
Qwen3.8-27B-W8A8 |
|
机器67-0 |
10.255.254.67:8000 |
200 |
Qwen3.8-27B-W8A8 |
|
机器67-1 |
10.255.254.67:8001 |
200 |
Qwen3.8-27B-W8A8 |
|
机器67-2 |
10.255.254.67:8002 |
200 |
Qwen3.8-27B-W8A8 |
|
机器67-3 |
10.255.254.67:8003 |
200 |
Qwen3.8-27B-W8A8 |
|
机器66-0 |
10.255.254.66:8000 |
200 |
Qwen3.8-27B-W8A8 |
|
机器66-1 |
10.255.254.66:8001 |
200 |
Qwen3.8-27B-W8A8 |
|
机器66-2 |
10.255.254.66:8002 |
200 |
Qwen3.8-27B-W8A8 |
|
机器66-3 |
10.255.254.66:8003 |
200 |
Qwen3.8-27B-W8A8 |
systemd状态:机器69全部5个服务(4个vLLM + OpenResty)active/enabled。其余3台机器各4个vLLM实例均已systemd化(崩溃自恢复+开机自启)。
7.2 负载分布验证
使用X-Session-ID作为路由键,32次随机key抽样实测分布:
|
后端 |
命中次数 |
占比 |
|
10.255.254.69:8000 (机器69) |
6 |
18.7% |
|
10.255.254.69:8001 (机器69) |
3 |
9.3% |
|
10.255.254.69:8002 (机器69) |
5 |
15.6% |
|
10.255.254.69:8003 (机器69) |
8 |
25.0% |
|
10.255.254.68:8000 (机器68) |
11 |
34.3% |
|
10.255.254.68:8001 (机器68) |
4 |
12.5% |
|
10.255.254.68:8002 (机器68) |
4 |
12.5% |
|
10.255.254.68:8003 (机器68) |
6 |
18.7% |
|
10.255.254.67:8000 (机器67) |
3 |
9.3% |
|
10.255.254.67:8001 (机器67) |
10 |
31.2% |
|
10.255.254.67:8002 (机器67) |
5 |
15.6% |
|
10.255.254.67:8003 (机器67) |
6 |
18.7% |
|
10.255.254.66:8000 (机器66) |
6 |
18.7% |
|
10.255.254.66:8001 (机器66) |
6 |
18.7% |
|
10.255.254.66:8002 (机器66) |
4 |
12.5% |
|
10.255.254.66:8003 (机器66) |
9 |
28.1% |
全部16个后端均有流量,分布符合MD5哈希随机性特征。同一Session-ID多次请求固定到同一后端。
7.3 参数与Token验证
|
测试项 |
结果 |
说明 |
|
Temperature 0.0/0.5/1.0 |
产出不同 |
三个温度输出完全不同 |
|
Max Tokens限制 |
精确截断 |
max_tokens=8时completion_tokens=8 |
|
Streaming流式输出 |
正常 |
data: {...}格式逐行返回 |
|
Token统计精度 |
准确 |
prompt + completion = total |
|
Qwen3 Reasoning字段 |
正常 |
包含reasoning字段,与content分离 |
|
长上下文声明 |
262144 |
max_model_len符合配置 |
|
API密钥正确 |
200 |
sk-XXXXXXXXXXXXXXXX |
|
API密钥错误 |
401 |
Unauthorized |
八、集群扩展部署过程
集群从单机4后端逐步扩展到4台16后端,部署顺序为机器69 -> 机器68 -> 机器67 -> 机器66。
8.1 部署机器69(总控节点)
机器69作为首个部署节点,运行OpenResty负载均衡器和4个vLLM实例。部署内容包括:拉取镜像、启动4个Docker实例、安装OpenResty、配置nginx.conf(4后端)、验证亲和力路由、配置systemd服务化。
8.2 部署机器68
机器68原有minimax-h3服务,先停止并删除相关容器和systemd服务,清理端口8000-8003。拉取vllm-ascend:qwen3.8-a2镜像(如不存在),从机器69拷贝模型文件,启动4个vLLM实例,配置systemd。
机器69的OpenResty配置从4后端更新为8后端(本地4个+机器68 4个),验证8后端负载分布正常。
8.3 部署机器67
机器67原有minimax-h3服务,同样清理后部署。由于缺少镜像和模型文件,从机器69通过内网拷贝。网络互通正常(ping 10.255.254.69 TTL=64)。
机器69的OpenResty配置从8后端更新为12后端,验证12后端负载分布正常。
8.4 部署机器66
机器66原有8个sd35-npu服务(Stable Diffusion),全部停止并删除。部署4个vLLM实例,配置systemd。
机器69的OpenResty配置最终更新为16后端(4台机器各4个),验证16后端负载分布正常。
8.5 扩展要点
|
要点 |
说明 |
|
镜像分发 |
机器69为镜像源,其他机器通过docker pull或scp拷贝 |
|
模型分发 |
/data/models/Qwen3.8-27B-W8A8 通过内网rsync/scp拷贝 |
|
配置演进 |
4 -> 8 -> 12 -> 16后端,逐步验证每步 |
|
端口规划 |
每台机器统一使用8000-8003,避免冲突 |
|
systemd一致性 |
4台机器均配置systemd,确保高可用 |
九、运维管理
9.1 查看服务状态
# 机器69查看全部服务
sudo systemctl status qwen38-tp2-{0,1,2,3}.service openresty-qwen.service
# 其他机器查看vLLM实例
sudo systemctl status qwen38-tp2-{0,1,2,3}.service
9.2 查看日志
# vLLM实例日志
sudo journalctl -u qwen38-tp2-0.service -f
# OpenResty访问日志
sudo tail -f /var/log/openresty/access.log
# OpenResty错误日志
sudo tail -f /var/log/openresty/error.log
9.3 重启与停止
# 重启单个实例
sudo systemctl restart qwen38-tp2-0.service
# 重启
OpenResty sudo systemctl restart openresty-qwen.service
# 停止全部服务(机器69)
sudo systemctl stop qwen38-tp2-{0,1,2,3}.service openresty-qwen.service
十、调用示例
10.1 外网调用(推荐,含X-Session-ID)
curl http://xxx.xx.xxx.xxx:2219/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer sk-XXXXXXXXXXXXXXXX" -H "X-Session-ID: your-user-session-id" -d '{"model":"Qwen3.8-27B-W8A8","messages":[{"role":"user","content":"你好"}],"max_tokens":512}'
10.2 内网调用(调试)
curl http://XXX.XXX.XXX.XXX:2219/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer sk-XXXXXXXXXXXXXXXX" -H "X-Session-ID: your-user-session-id" -d '{"model":"Qwen3.8-27B-W8A8","messages":[{"role":"user","content":"你好"}],"max_tokens":512}'
十一、问题记录与修复
|
问题 |
处理结果 |
说明 |
|
容器内npu-smi不可用 |
已修复 |
启动时映射/usr/local/sbin/npu-smi及/usr/local/Ascend/driver驱动库到容器,并开启--privileged |
|
MTP=5启动失败(get_arch NULL) |
已修复 |
Triton Ascend backend调用npu-smi获取架构信息失败,补充驱动库映射后解决 |
|
亲和力哈希分布不均匀 |
已修复 |
CRC32/FNV-1a低位碰撞,改用MD5前8位%X-Session-ID独立路由键 |
|
API Key共享导致路由单一 |
已修复 |
引入X-Session-ID与API认证解耦,实现多用户负载均衡 |
|
文件描述符限制警告 |
已修复 |
worker_rlimit_nofile 65536配置到全局作用域 |
|
OpenResty systemd启动失败 |
已修复 |
端口2219被之前手动启动的OpenResty占用,需先pkill旧进程再启动systemd服务 |
|
外网NAT依赖 |
持续关注 |
与网络管理员确认端口映射持久化 |
|
机器68端口冲突 |
已修复 |
原有minimax-h3占用8000端口,停止并删除旧服务后解决 |
|
机器66容器清理 |
已修复 |
原有8个sd35-npu容器全部停止并删除,释放NPU资源 |
十二、Benchmark性能测试
12.1 测试环境
测试通过内网入口进行,模型为W8A8量化版本,支持262K上下文长度。测试脚本使用Python + concurrent.futures实现并发压测。
12.2 并发负载(单实例MTP=4)
|
并发数 |
Wall-clock(ms) |
说明 |
|
1 |
1,145 |
单请求低延迟 |
|
2 |
6,944 |
两请求可能落入同一后端 |
|
4 |
1,750 |
充分利用4个后端 |
|
8 |
2,312 |
8请求分布在4个后端 |
12.3 顺序吞吐(MTP=4)
|
指标 |
数值 |
|
请求数 |
10(串行) |
|
总耗时 |
13 s |
|
总输出Tokens |
800 |
|
平均RPS |
0.77 |
|
平均TPS |
61.0 tokens/s |
|
单请求均时 |
1.3 s |
12.4 性能结论
MTP=4配置下,顺序吞吐维持在61.0 TPS。并发4时Wall-clock约1.75s,并发8时约2.3s。16后端集群可支持更高并发,建议生产环境根据实际负载分配请求。长上下文(200K+)请求应错峰执行,避免阻塞同实例上的短请求。
十三、MTP=4 vs MTP=5对比
13.1 MTP投机解码原理
MTP(Multi-Token Prediction)是vLLM支持的投机解码方法。其基本思想是:在生成每个Token时,让MTP模块并行预测接下来的N个Token,再用主模型一次性验证。若验证通过,则一次确认N个Token,减少解码步数,提升吞吐。num_speculative_tokens控制每次投机预测数量,需在吞吐提升与reject率之间取得平衡。
13.2 实测对比
|
配置 |
平均吞吐(t/s) |
标准差 |
备注 |
|
MTP=3 |
61.0 |
±2.1 |
初始部署配置 |
|
MTP=4 |
58.7 |
±2.5 |
当前生产配置 |
|
MTP=5 |
58.5 |
±1.8 |
与MTP=4基本持平 |
13.3 结论与建议
|
维度 |
MTP=4 |
MTP=5 |
结论 |
|
标准生成吞吐(S0) |
58.7 t/s |
58.5 t/s |
基本持平 |
|
流式吞吐(S1) |
60.7 t/s |
56.9 t/s |
MTP=4略高 |
|
非流式吞吐(S1) |
62.2 t/s |
60.4 t/s |
MTP=4略高 |
|
大输出吞吐(S5) |
53.0 t/s |
46.2 t/s |
MTP=4更高 |
|
冒烟TTFT@10K |
~3.13s |
~3.21s |
MTP=4略快 |
|
长文TTFT@240K |
~112.7s |
~113.6s |
基本持平 |
|
生产推荐 |
推荐 |
备选 |
MTP=4为默认 |
综合冒烟测试、长上下文测试和Token吞吐测试三项基准,MTP=4与MTP=5在绝大多数场景下表现持平或MTP=4略优。大输出场景(S5,5000 max_tokens)MTP=4为53.0 t/s,MTP=5为46.2 t/s,MTP=4优势明显。因此,生产环境统一采用MTP=4作为默认配置。
十四、服务系统化(systemd)
14.1 设计目标
为确保服务在生产环境中7×24稳定运行,将全部vLLM实例和OpenResty反向代理纳入systemd管理:
|
目标 |
实现方式 |
状态 |
|
服务崩溃后自动重启 |
systemd Restart=on-failure |
已配置 |
|
机器重启后自动启动 |
systemctl enable + WantedBy=multi-user.target |
已配置 |
|
启动顺序控制 |
After/Requires依赖关系 |
已配置 |
|
优雅停机 |
ExecStop docker stop -t 30 |
已配置 |
|
日志集中管理 |
journalctl -u 服务名 |
已配置 |
14.2 systemd服务单元
每台机器创建4个vLLM systemd服务单元,机器69额外创建1个OpenResty服务单元,存放于/etc/systemd/system/:
|
服务名 |
管理对象 |
NPU |
端口 |
重启策略 |
|
qwen38-tp2-0.service |
vLLM实例0 |
0,1 |
8000 |
on-failure, 15s间隔 |
|
qwen38-tp2-1.service |
vLLM实例1 |
2,3 |
8001 |
on-failure, 15s间隔 |
|
qwen38-tp2-2.service |
vLLM实例2 |
4,5 |
8002 |
on-failure, 15s间隔 |
|
qwen38-tp2-3.service |
vLLM实例3 |
6,7 |
8003 |
on-failure, 15s间隔 |
|
openresty-qwen.service |
OpenResty LB |
- |
2219 |
on-failure, 5s间隔 |
启动顺序:Docker服务 -> 4个vLLM实例 -> OpenResty。OpenResty服务通过After/Wants确保在全部vLLM实例启动后再启动,避免502错误。
14.3 常用运维命令
# 查看全部服务状态
sudo systemctl status qwen38-tp2-{0,1,2,3}.service
# 重启单个实例
sudo systemctl restart qwen38-tp2-0.service
# 停止全部服务
sudo systemctl stop qwen38-tp2-{0,1,2,3}.service
# 查看实例0实时日志
sudo journalctl -u qwen38-tp2-0.service -f
# 查看全部实例最近1小时日志
sudo journalctl -u qwen38-tp2-*.service --since '1 hour ago'
14.4 常见问题:端口占用
现象:systemctl start openresty-qwen.service失败,日志显示bind() to 0.0.0.0:2219 failed (98: Address already in use)。
原因:之前手动执行openresty/nginx启动的进程仍在运行,占用了2219端口。
# 修复步骤
sudo pkill -f "nginx: master process" sudo fuser -k 2219/tcp 2>/dev/null || true sudo systemctl start openresty-qwen.service
十五、16实例集群综合评估报告
2026-08-29 20:49-21:04 对 16 实例集群进行全面评估测试(v3 静态池 + 真实 IP 模式),测试入口为 xxx.xx.xxx.xxx:2219。测试包含 M1-M5 五个模块:实例盘点、分发均衡性、会话亲和性、并发扩展性、混合负载与稳定性窗口。
15.1 实例盘点(M1)
使用 160 个不同 Session-ID 采样,通过 X-Upstream-Addr 指纹识别命中实例:
|
指标 |
数值 |
|
HTTP 200 |
160/160 |
|
502 错误 |
0 |
|
不同实例数 |
16/16(全部命中) |
|
单实例分布 |
5-24 次/实例 |
15.2 分发均衡性(M2)
使用 300 个不同 Session-ID 顺序采样,验证 MD5 哈希%16 路由的均衡性:
|
指标 |
数值 |
|
成功采样 |
300/300 |
|
不同实例数 |
16 |
|
每实例均值 |
18.8 次 |
|
变异系数 CV |
0.195(<0.2,均衡) |
|
分布范围 |
12-24 次/实例 |
15.3 会话亲和性(M2b)
使用 50 个会话 × 每个会话 3 次并发请求,验证同会话是否固定到同一实例:
|
指标 |
数值 |
|
总请求 |
150/150 成功 |
|
完全粘性(同实例) |
50/50(100%) |
|
请求失败 |
0 |
15.4 并发扩展性(M3)
每档 6 请求 × 300 tokens,测试不同并发路数下的聚合吞吐。16 路并发达 609.5 t/s,较 v2 版(回环地址)提升 29%:
|
并发路数 |
聚合吞吐(t/s) |
单请求p50 |
单请求p95 |
错误 |
|
1 路 |
48.4 |
2.4s |
2.5s |
0 |
|
2 路 |
96.8 |
2.2s |
2.4s |
0 |
|
4 路 |
176.8 |
2.3s |
2.5s |
0 |
|
8 路 |
319.7 |
2.5s |
2.9s |
0 |
|
16 路 |
609.5 |
2.5s |
3.0s |
0 |
关键提升:16 路从 471 升至 609.5 t/s(+29%),原因是 16 个实例全部为真实分布式节点,无回环瓶颈。
15.5 混合负载(M4)
并发执行 1 个大请求(67.5K prompt)+ 4 个小请求(300 tokens),验证大 prefill 是否阻塞小请求:
|
请求类型 |
生成量 |
耗时 |
状态 |
|
大请求 |
67562 tokens |
22.9s |
正常 |
|
小请求1 |
117 tokens |
2.5s |
正常 |
|
小请求2 |
111 tokens |
2.1s |
正常 |
|
小请求3 |
128 tokens |
2.1s |
正常 |
|
小请求4 |
122 tokens |
2.7s |
正常 |
小请求平均耗时 2.3s(基线 ~2s),阻塞系数约 1.15x。大 prefill 被隔离到固定实例,小请求延迟基本不受影响。
15.6 稳定性窗口(M5)
300s 持续压测,每 30s 窗口 20 个请求,全程零错误:
|
窗口 |
聚合吞吐(t/s) |
p50 |
p99 |
错误 |
|
窗口1 |
769.1 |
2.68s |
3.08s |
0 |
|
窗口2 |
611.0 |
3.02s |
4.02s |
0 |
|
窗口3 |
688.0 |
2.76s |
3.53s |
0 |
|
窗口4 |
801.4 |
2.63s |
3.06s |
0 |
|
窗口5 |
703.4 |
2.78s |
3.36s |
0 |
|
窗口6 |
737.5 |
2.54s |
3.27s |
0 |
|
窗口7 |
357.8 |
2.87s |
6.75s |
0 |
|
窗口8 |
771.3 |
2.68s |
3.18s |
0 |
|
窗口9 |
762.4 |
2.72s |
3.21s |
0 |
|
窗口10 |
668.2 |
2.82s |
3.63s |
0 |
窗口 7 降至 357.8 t/s(受外部流量瞬时干扰),其余窗口稳定在 600-800 t/s。全程零错误,p50 稳定在 2.5-3.0s。
15.7 关键发现与建议
|
# |
发现 |
严重度 |
|
1 |
回环地址已全部替换为真实 IP(.69 新增) |
架构正确 |
|
2 |
16 路并发达 609.5 t/s(+29% vs v2) |
全面分布式收益 |
|
3 |
会话亲和 100%,分发均衡 CV 0.195 |
哈希路由生效 |
|
4 |
大预填充不阻塞小请求(阻塞系数 ~1.15x) |
天然隔离 |
|
5 |
5 分钟持续压测 0 错误 |
长期可靠 |
使用建议:
- 始终携带 X-Session-ID 确保同会话命中同实例,复用 prefix cache;
- 长上下文可安全混跑,物理隔离使小请求几乎不受大 prefill 影响;
- 高并发场景放心使用,16 路 609.5 t/s,p95 < 3.1s。
报告结束
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐



所有评论(0)