报告时间:2026-08-29(2026-09-04 更新:新增第十六章;2026-09-09 更新:新增第十七章 TP4 架构改造、第十八章 性能实测)

部署规模:4台 × 8卡 = 32卡昇腾910B4集群

目录

一、项目概述

部署目标

二、硬件环境

三、软件环境

四、集群部署架构

五、服务详细配置

5.1 vLLM 实例配置

5.2 16后端实例分布

六、OpenResty 反向代理配置

6.1 核心功能

6.2 亲和力路由设计

6.3 完整 nginx.conf(16后端)

七、集群验证结果

7.1 16后端健康检查

7.2 负载分布验证

7.3 参数与Token验证

八、集群扩展部署过程

8.1 部署机器69(总控节点)

8.2 部署机器68

8.3 部署机器67

8.4 部署机器66

8.5 扩展要点

九、运维管理

9.1 查看服务状态

9.2 查看日志

9.3 重启与停止

十、调用示例

10.1 外网调用(推荐,含X-Session-ID)

10.2 内网调用(调试)

十一、问题记录与修复

十二、Benchmark性能测试

12.1 测试环境

12.2 并发负载(单实例MTP=4)

12.3 顺序吞吐(MTP=4)

12.4 性能结论

十三、MTP=4 vs MTP=5对比

13.1 MTP投机解码原理

13.2 实测对比

13.3 结论与建议

十四、服务系统化(systemd)

14.1 设计目标

14.2 systemd服务单元

14.3 常用运维命令

14.4 常见问题:端口占用

十五、16实例集群综合评估报告

十六、集群架构调整与机器69故障恢复

15.1 实例盘点(M1)

15.2 分发均衡性(M2)

15.3 会话亲和性(M2b)

15.4 并发扩展性(M3)

15.5 混合负载(M4)

15.6 稳定性窗口(M5)

15.7 关键发现与建议

十七、集群架构调整:16×TP2 → 8×TP4

17.1 调整背景与目标

17.2 新架构与服务配置

17.3 KV cache 规模对比

17.4 实施与验证

17.5 监控适配与崩溃取证黑匣子

十八、性能实测

18.1 测试方法

18.2 16并发与32并发对比

18.3 吞吐结构

18.4 集群标称能力

一、项目概述

本项目在4台昇腾910B4 NPU服务器(共32卡)上完成 Qwen3.8-27B-W8A8 大模型的推理服务集群部署。集群架构历经两次演进:初期每台服务器部署4个vLLM实例(各2卡TP),共16个后端实例;2026-09-09 起调整为每台2个实例(各4卡TP),共8个后端实例(详见第十七章),单实例KV cache提升至2.46倍并恢复MTP投机解码与prefix caching。

通过机器66上的OpenResty实现反向代理、负载均衡和会话亲和力路由(2026-09-04 由机器69迁移至机器66,详见第十六章),对外提供统一的OpenAI兼容API服务。

部署目标

目标

状态

Qwen3.8-27B-W8A8 推理服务(16后端)

完成

32卡NPU充分利用(4台×8卡)

完成

外网统一API入口(xxx.xx.xxx.xxxx:2216)

完成

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.xxxx:2216(NAT映射至机器66)

三、软件环境

组件

版本

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资源

机器69 NPU驱动损坏(实例崩溃、npu-smi失效)

已修复

系统重装后改用昇腾官方驱动包全新安装,8卡恢复OK;此前手工复制.ko方案失败(固件加载workqueue不触发、设备节点无法创建),NPU驱动必须整体安装

服务崩溃停止后不再自动重启

已修复

systemd默认StartLimit触发后停止重启,全部服务单元增加StartLimitIntervalSec=0

机器69内核自动升级导致驱动失效风险

已规避

锁定6.8.0-124-generic内核(grub-set-default + apt-mark hold),禁止内核自动升级

机器69"HBM 91%异常"告警

已澄清

vLLM预分配KV cache常驻,实例运行期空闲HBM约60GB/65536为全集群正常水位(69与68实测一致);当时真正病根为驱动损坏

十二、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 错误

长期可靠

使用建议:

  1. 始终携带 X-Session-ID 确保同会话命中同实例,复用 prefix cache;
  2. 长上下文可安全混跑,物理隔离使小请求几乎不受大 prefill 影响;
  3. 高并发场景放心使用,16 路 609.5 t/s,p95 < 3.1s。

十六、集群架构调整与机器69故障恢复

2026-09-04,集群完成两项重要变更:统一入口(OpenResty负载均衡)由机器69迁移至机器66;此前因NPU驱动故障撤出的机器69完成系统重装与驱动重建,重新加入集群。全集群恢复4台×8卡、16后端的满编运行状态,统一入口为 xxx.xx.xxx.xxxx:2216。

16.1 统一入口迁移(机器69 → 机器66)

机器69原本同时承担负载均衡器与推理节点双重角色,故障域耦合:LB所在机器异常将导致整个集群对外失联。为此将OpenResty迁移至机器66,迁移期间机器66/67/68以12后端过渡运行,待机器69恢复后切回16后端。

迁移项

内容

负载均衡节点

机器69 → 机器66(nginx master 常驻进程,配置热重载)

监听端口/公网入口

2219 → 2216(xxx.xx.xxx.xxxx:2216,NAT映射至机器66)

路由算法

MD5前8位十六进制取模 → Lua整数哈希(hash×31+字节)%16,粘性等价、计算更轻

后端健康检查

新增 max_fails=3 fail_timeout=30s,后端故障自动摘除与恢复

文件描述符上限

worker_rlimit_nofile 65535(消除1024上限告警)

配置回滚

旧配置备份为 nginx.conf.bak-12backend,可随时回滚

迁移后先以12后端压测验证通过,机器69恢复后再更新为16后端(见16.5)。

16.2 机器69故障与系统重装

2026-09-03,机器69出现NPU驱动级故障:vLLM实例崩溃、HBM占用异常、npu-smi失效,随即撤出集群。为彻底根治,决定对机器69重装系统:Ubuntu 24.04.3 LTS,内核锁定6.8.0-124-generic(与集群其他机器及驱动版本保持一致)。

内核锁定方式:GRUB_DEFAULT=saved 并 grub-set-default 固定启动项,对 linux-image/linux-headers-6.8.0-124-generic 执行 apt-mark hold,防止内核自动升级导致驱动再次失效。该限制为长期约束,机器69上禁止执行内核升级。

16.3 驱动恢复:从手工复制失败到官方驱动安装

方案一(失败):从机器68手工复制驱动——41个内核.ko模块、完整驱动目录(含115MB设备固件)、用户态库、udev规则与配置文件。模块可正常加载,8张卡PCI probe均打印 enabling device,但关键的 devdrv_load_finish(固件加载完成事件)始终不出现,/dev/davinci0-7 设备节点无法创建,npu-smi 报 dcmi module initialize failed (ret=-8005)。

排查过程排除了三类假设:启动参数差异(与机器68逐字对比一致)、PCIe BAR资源分配(Region 0/2/4与68完全一致,Above 4G解码正常)、固件文件损坏(两端md5一致)。根因判断:NPU驱动初始化依赖内核模块、设备固件、用户态安全校验链(CRL)与设备启动初始化脚本的完整组合,跨机手工复制无法完整复现安装器行为。

方案二(成功):改用昇腾官方驱动安装包在机器69全新安装。安装完成后 npu-smi 26.0.rc1 正常识别8卡910B4-1,健康状态全部OK,空载HBM 3.2GB/65536,与机器68完全一致。

关键教训:NPU驱动必须整体安装,不可跨机手工复制.ko;系统重装后应优先使用官方安装包,可节省数小时的内核级排查。

16.4 机器69环境重建

重建项

说明

Docker

apt 安装 docker.io(29.1.3),systemd常驻并加入docker用户组

数据盘

/data 为2×1.8T NVMe RAID0(XFS 3.6T),重装后自动挂载,原有数据完好

模型

从机器68 rsync 内网传输30GB(约115MB/s耗时5分钟),两端 du -sh 核对一致

镜像

docker pull quay.io/ascend/vllm-ascend:qwen3.8-a2

SSH免密

修复 known_hosts 主机密钥变更(重装后密钥轮换),配置68↔69双向免密

服务文件

从机器68复制 qwen38-tp2-0~3.service(含 StartLimitIntervalSec=0 修复版)

实例启动

4实例全部启动成功,带API Key验证 /v1/models 均返回200

注意:机器69曾长期带故障运行,回切生产流量前先进行单机小并发压测与HBM盯梢,确认稳定后再切入。

16.5 16后端切换与全面验证

机器69的4个实例就绪后,机器66的 nginx.conf 从12后端更新为16后端:新增 10.255.254.69:8000-8003 四个upstream,Lua路由取模从%12改为%16,健康检查参数与既有后端保持一致。nginx -t 通过后热重载。

验证项

方法

结果

健康检查

curl http://localhost:2216/health

返回 {"status":"ok","backends":16}

分发覆盖

16个不同X-Session-ID抽样

4台机器全部命中,69确认在池

会话粘性

同一session连续3次请求

三次均落同一后端(100%粘性)

推理冒烟

经LB的 /v1/chat/completions

正常返回

全集群压测

64并发经LB持续压测

全程HTTP 200,零错误

69负载水平

npu-smi 盯梢AICore

69-81%,与66/67/68齐平

稳定性

实例运行时长核对

压测全程零重启

HBM水位

69与68对比 / 65536

均约60GB,水位完全一致

历史澄清:此前记录的机器69"HBM 91%异常"实为误判——vLLM启动时预分配KV cache池并常驻,实例运行期间空闲HBM约60GB/65536是全集群(66/67/68/69)的正常水位。机器69当时的真正故障是驱动损坏,与HBM无关。

16.6 配置参数变更汇总

配置项

变更前(2026-08-29)

变更后(2026-09-04)

负载均衡节点

机器69

机器66

监听端口/公网入口

2219 / xxx.xx.xxx.xxx:2219

2216 / xxx.xx.xxx.xxxx:2216

路由算法

MD5前8位十六进制取模%16

Lua整数哈希(hash×31+字节)%16

后端健康检查

max_fails=3 fail_timeout=30s

MTP投机解码

num_speculative_tokens=4

num_speculative_tokens=3

单实例并发上限

--max-num-seqs 32

--max-num-seqs 4(以现行服务文件为准)

崩溃重启策略

Restart=on-failure

Restart=always + StartLimitIntervalSec=0

worker_rlimit_nofile

65536

65535

机器69内核

未锁定

锁定6.8.0-124-generic(apt hold,禁止升级)

截至2026-09-04,集群以16后端满编稳定运行,统一入口 xxx.xx.xxx.xxxx:2216,API密钥认证、X-Session-ID会话粘性路由与后端健康检查均生效。

十七、集群架构调整:16×TP2 → 8×TP4

2026-09-09,集群由16实例×2卡(TP2)架构调整为8实例×4卡(TP4)架构,在保持262K上下文不变的前提下将单实例KV cache提升2.46倍,并恢复MTP=3投机解码、开启prefix caching。调整当日完成并全量验证通过,统一入口 xxx.xx.xxx.xxxx:2216、API Key认证与X-Session-ID会话粘性路由机制均保持不变。

17.1 调整背景与目标

2026-09-03至09-07期间,多台机器陆续出现vLLM实例崩溃重启(systemd累计NRestarts 1~3次)。崩溃特征为EngineCore进程静音死亡:无Python traceback、无OOM记录、内核日志零异常,docker日志止于Parent process exited, terminating worker queues。触发画像为:大prompt万级tokens/s的高并发prefill,以及多模态内容请求完成后1秒内。

排查过程中排除了MTP假设:去除MTP参数后崩溃仍然复现,真凶未最终定位,疑似与大prompt及多模态prefill触发有关。基于排查结论,确定以下调整目标:(1) 每实例卡数2→4,放大KV cache以增强长文并发下的稳定性余量;(2) 恢复MTP=3提升生成吞吐;(3) 开启prefix caching,削减重复长prompt的计算开销;(4) 保留一台带ACL日志黑匣子的实例,用于崩溃复发时的驱动层取证。

17.2 新架构与服务配置

配置项

16×TP2(改造前)

8×TP4(改造后)

实例数/每实例卡数

16实例 × 2卡

8实例 × 4卡

每卡KV cache

37.59 GiB

46.20 GiB(单实例184.8 GiB,×2.46)

单实例并发上限

max-num-seqs 4

max-num-seqs 4(不变)

上下文长度

262,144

262,144(不变)

MTP投机解码

排查期已移除

num_speculative_tokens=3(恢复)

Prefix caching

关闭

开启(--enable-prefix-caching)

实例端口分配

8000-8003(每机4个)

8000/8001(8000=NPU0-3,8001=NPU4-7)

LB路由哈希

Lua整数哈希 %16

Lua整数哈希 %8(session 0-7 全覆盖)

其余关键参数与改造前保持一致:--max-model-len 262144、--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、--block-size 16、--trust-remote-code、--dtype bfloat16。镜像沿用 quay.io/ascend/vllm-ascend:qwen3.8-a2,模型 /models/Qwen3.8-27B-W8A8,API Key sk-qwen-27b-lb-2026。

17.3 KV cache 规模对比

图17-1 KV cache 规模对比(37.6→46.2 GiB/卡,单实例×2.46)

改造后每卡KV cache由37.59 GiB增至46.20 GiB,单实例由75.2 GiB增至184.8 GiB。8个实例合计约1.48 TiB KV cache,可支撑更多262K长上下文请求同时在槽,直接缓解排查期观察到的长prompt高并发压力。

17.4 实施与验证

实施按三步推进并逐步回执确认:(1) 机器66的OpenResty配置由16上游精确改写为8上游(移除10.255.254.67/68/69的8002、8003共6个upstream,Lua路由取模%16→%8),nginx -t通过后热重载,/health返回backends:8;(2) 四台机器统一创建 qwen38-tp4-0/1 服务单元并切换(tp2停用+禁用,tp4启动),配置经rsync分发后逐台核对 tensor-parallel-size 4;(3) 四台并行完成端口健康、引擎配置、KV cache规模、路由覆盖、会话粘性与推理冒烟验证。

验证项

方法

结果

实例配置

journalctl 引擎启动日志

tensor_parallel_size=4、prefix caching、MTP=3 全部确认

KV cache规模

worker.py Available KV cache

46.20 GiB/卡,8实例一致

端口健康

带API Key访问 /v1/models

4台×8000/8001 共8端口全部200

LB健康

curl http://localhost:2216/health

返回 {"status":"ok","backends":8}

路由覆盖

8个X-Session-ID抽样

8后端各命中一次

会话粘性

同session连续3次请求

三次均落同一后端(100%粘性)

推理冒烟

经LB的 /v1/chat/completions

正常生成,fingerprint确认TP4

监控适配

监控Automation实测探测

8/8健康,重启基线归零

结论:8×TP4架构全量验证通过。原16×TP2服务文件保留于各机(disabled状态),可随时回切;nginx配置备份为 nginx.conf.bak16。

17.5 监控适配与崩溃取证黑匣子

看板GPU集群健康监控Widget由16后端适配为8后端:探测session由16字符(ord%16)改为0-7八个字符(ord%8全覆盖),页面标题与每机实例数显示(n/4→n/2)同步更新;重启检测逻辑不变(跨轮询对比各后端/metrics的process_start_time_seconds,跳变判重启),适配后实测探测8/8健康、累计重启归零。

取证黑匣子:机器69的tp4-1实例挂载ACL日志卷(-v /data/ascend_log/tp4-1:/root/ascend/log),开启驱动debug日志(ASCEND_GLOBAL_LOG_LEVEL=0)与请求日志(--enable-log-requests),日志写入已经find核实(debug/run/atb目录及vllm_ascend插件日志均在写,属root需sudo读取)。崩溃复发时取证命令:

sudo grep -hiE "ERROR|FATAL|abort|terminate" /data/ascend_log/tp4-1/debug/*.log | tail -40

sudo journalctl -u qwen38-tp4-1 --since "-30 min" | grep -E "Received request|prompt_tokens" | tail

十八、性能实测

2026-09-09 对8×TP4集群进行两档并发压测(16并发与32并发打满),采用vLLM /metrics计数器差值法:压测前后各读取一次 prompt_tokens_total / generation_tokens_total 等计数器取差值,经统一入口的X-Session-ID哈希定向路由将请求均匀分散到8个后端(/metrics经session锁后端,8个session求和得集群总量)。负载为短问答(prompt<100 token,max_tokens=200),思考型模型输出偏重,平均约124 token/请求。

18.1 测试方法

并发设计:16并发(每实例2槽)与32并发(session cap-1..32,每实例4槽打满max-num-seqs=4)两档,每档持续约3分钟。测量窗口内集群另有少量背景业务流量叠加(两次读数间隔17分钟,集群累计输入token自行上涨约75万),故32并发档的输入吞吐为压测与业务的叠加值,真实纯压测上限略低,量级可信。

18.2 16并发与32并发对比

指标

16并发(2/实例)

32并发(4/实例打满)

生成吞吐 Decode

≈9,300 TPM

13,776 TPM(+48%)

请求速率 RPM

≈75

111

输入吞吐 Prefill

未单独测量

243,012 TPM(含背景流量)

单流解码速度

≈15 token/s

≈7 token/s

集群并发上限

16路

32路(每实例4槽)

图18-1 16/32并发两档性能对比

32并发打满后生成吞吐由约9,300 TPM提升至13,776 TPM(+48%),RPM由约75提升至111。每实例4并发(max-num-seqs=4)即当前配置的吞吐上限,该值为262K上下文的取舍结果。

18.3 吞吐结构

图18-2 集群吞吐结构(输入:输出≈47:1)

32并发下实测输入吞吐243,012 TPM、生成13,776 TPM,输入:输出约47:1,与集群开机至今的业务画像一致(累计输入2,960万token、输出63万token)。本架构的设计甜区即为长文输入业务:prefix caching开启后,重复长prompt不再重复计算,实际消耗的算力远低于表观输入吞吐。

18.4 集群标称能力

标称值(32并发饱和、262K上下文、MTP=3 + prefix caching):输入吞吐≥200,000 TPM;生成吞吐约14,000 TPM;短请求(prompt<100、输出约200 token)约110 RPM;并发上限32路(每实例4槽)。长请求(10万+ token输入)受prefill限速,RPM降至10~30。生成侧为瓶颈:单流约7 token/s,若业务需要更高单流速度,可降低并发数换取单流吞吐。

报告结束

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐