Atlas 800 A2 单机部署 GLM-5.3-Flash:六轮调优(含最优启动参数)
Atlas 800 A2 单机部署 GLM-5.3-Flash:六轮调优(附最优启动参数)
8 张昇腾 910B3,六轮参数迭代。
这篇记录了我们如何把一个连智能体调用都跑不起来的部署,调到冷启动 12.7 万 token 稳定服务的全过程。网上 Atlas 800 A2 + vLLM-Ascend 的完整实测资料很少,多数停留在"能跑起来"。希望这篇能补上"跑得稳"这一块。
一、环境交代
| 项目 | 配置 |
|---|---|
| 服务器 | Atlas 800 A2 |
| NPU | 8 × 昇腾 910B3(64GB HBM/卡) |
| 模型 | GLM-5.3-Flash-w8a8(昇腾量化版) |
| 推理框架 | vLLM 0.23.0(vllm-ascend,TP8 + Expert Parallel) |
| 互联 | HCCS 392GB/s |
二、初次尝试,发现问题:单机部署KV 缓存池受限
选定部分常用参数启动相当顺利:8 张卡全部识别,权重正常加载,服务起来后用普通对话一问一答,响应飞快——看上去万事俱备。
直到把服务接入真实的智能体业务,问题才暴露出来:
| 场景 | 表现 |
|---|---|
| 普通对话 | ✅ 秒回,一切正常 |
| 智能体调用(带 tools) | ❌ 一直转圈,直到超时 |
而服务端日志显示的是成功:
POST /v1/chat/completions 200 OK
客户端却一个字都收不到,同时引擎侧毫无动静——GPU 利用率 0%,没有任何生成活动。
第一反应是工具调用解析器出了问题(GLM 系列的 parser 历史上确实有流式中断的 bug)。我们按这个方向查了一轮,把 parser、端点、tool_choice 各模式全测了一遍——全部正常。方向错了。
真正的突破口是 vLLM 的 Prometheus 指标:
curl -s http://<host>:8077/metrics | grep cache_config_info
返回里有这么一行:
num_gpu_blocks = 1146
block_size = 4
1146 × 4 = 4584 tokens。
而我们的 --max-model-len 配置是多少?133120(声称支持 133K)。
也就是说:这个服务声称能处理 13 万 token 的上下文,但它的 KV 缓存池实际只能装 4584 个 token。
再对照调度指标,真相彻底清晰:
vllm:num_requests_running = 0
vllm:num_requests_waiting = 1 (reason = "capacity")
vllm:kv_cache_usage_perc = 0.0
请求被调度器以 capacity(容量不足) 为由拒绝进入 GPU,永远排在等待队列里。
为什么普通对话正常、智能体必挂?
- 普通对话提示词:几十 token → 轻松进入 4584 的池子 ✅
- 智能体系统提示词 + 工具定义:轻松破万 → 永远进不去 ❌
阈值实测精确落在 4584,与 KV 池容量完全吻合。
顺带发现:队头阻塞
更糟的是,我们把一个超限请求打进队列后,再发一个只有 2 个 token 的"你好"——也被堵死了 30 秒。
一个超限请求,能毒化整个服务。
那句"200 OK"是怎么回事?
流式响应(SSE)的 HTTP 头是立即返回的,所以日志上看到 200。但引擎侧从未真正调度这个请求,自然一个字节都吐不出来。
经验一:vLLM 出现「200 OK 但引擎零活动」,先去
/metrics看num_requests_waiting的reason和cache_config_info的 KV 池大小,比查解析器快十倍。
三、六轮参数迭代(核心)
这是我们调试的完整轨迹。每一轮只改少数变量,用 KV 池大小、上下文悬崖位置、生成速度三个指标衡量。
| 轮次 | 关键配置 | KV 池 | 上下文悬崖 | 速度 |
|---|---|---|---|---|
| 0 初始 | batched-tokens 32768, util 0.9, MTP 开 | 4584 | 4.7K(智能体全挂) | — |
| 1 优化 | batched-tokens 8192, util 0.95, 去 MTP | 9072 | 消失(62.6K ✅) | — |
| 2 加回 MTP | +MTP, +cudagraph | 8524 | 45~48K | 28.6 tok/s |
| 3 再去 MTP | 去 MTP,保留 cudagraph | 9280 | 153~196K | 27.3 tok/s |
| 5 手工调参 | +前缀缓存,MTP 又开回来 | 5356 | 跳档 +5K 即挂 | 17.5 tok/s |
| 6 优化落地 ✅ | 删 MTP、len→131072、cudagraph[1…16] | 8956 | 126.9K(至上限无悬崖) | 20.9 tok/s |
第 4 轮是 KV 量化尝试(910B 无原生 FP8 计算单元,直接报错回退),未落地验证。
3.1 第一杠杆:–max-num-batched-tokens
这是最有效的一个参数,也是最容易被忽略的。
| 值 | KV 池 |
|---|---|
| 32768 | 4584 |
| 8192 | 9072(翻倍) |
原理:vLLM 启动时会用这个配置跑一次 profiling 前向,激活峰值决定了留给 KV 缓存的显存余量。32768 让激活峰值吃掉大半显存,KV 池就只剩 4584。降到 8192 后,让出的显存全部转化为 KV 池,同时 chunked prefill 生效,长提示词分块准入,挂死悬崖直接消失。
代价是长提示词 prefill 分更多块、略慢一点——但"连跑都跑不起来"和"慢一点",不用犹豫。
3.2 最大的反直觉:MTP 投机解码是负资产
我们一开始理所当然地认为投机解码能提速。实测数据打脸了。
第 2 轮 vs 第 3 轮只差一个 MTP,其他参数完全相同:
| 有 MTP | 无 MTP | |
|---|---|---|
| KV 池 | 8524 | 9280(+8.9%) |
| 上下文悬崖 | 45~48K | 153~196K(3.4 倍) |
| 生成速度 | 28.6 tok/s | 27.3 tok/s |
去掉 MTP,上下文悬崖提升 3.4 倍,代价只是 4.7% 的速度。
第 5 轮我们又(被动)验证了一次:MTP 开启时,草稿接受率只有 12.2%——1911 个草稿 token 里只接受了 234 个。等于白烧算力,还吃掉 KV 池。
经验二:在昇腾 NPU 上,MTP 投机解码的收益远不如 GPU。智能体场景下上下文是硬约束,用 4.7% 速度换 3.4 倍上下文余量,这笔交易非常划算。
3.3 最隐蔽的陷阱:前缀缓存让测试结果失真
第 5 轮我们测出"47K token 提示词通过",一度以为容量不错。后来发现这是假的。
原因:测试用的填充文本是重复段落,开启前缀缓存后被大量命中(实测命中率 72.5%),测的是"缓存加持下的极限",不是真实能力。
我们改进了测试方法——在每段文本前插入唯一递增序号([0000123]),让前缀无法复用:
| 模式 | 实测上限 |
|---|---|
| 热缓存(重复文本) | 49.4K |
| 冷启动(唯一文本) | ~13.4K |
差 3.8 倍。 真实业务里每次系统提示词 + 用户输入都不同,应该看冷启动的数据。
经验三:测长上下文上限,必须用唯一文本。否则前缀缓存会让结果虚高数倍。
3.4 最危险的行为:声明值远超实际容量
第 5 轮我们发现一个"状态依赖"的挂死:同一尺寸,跳档测就挂死、紧邻升序测就通过。
| 场景 | 增量 | 结果 |
|---|---|---|
| 23.4K → 31.3K | +6.9K | ❌ 挂死 |
| 43.9K → 45.9K → 47.4K | +2.0K / +1.4K | ✅ |
一次挂死实测持续了 2171 秒(36 分钟) 零输出,期间所有请求全部无响应。
根因:--max-model-len 声明了 262144,但真实能力只有十几 K。超限请求不会快速报错,而是无限挂死并毒化整个队列。
经验四:宁可把
max-model-len调小,让超限请求快速报 400,也不要让它挂死拖垮整个服务。
3.5 上下文阈值:131072 是怎么验证出来的
最终配置里的 --max-model-len 131072 不是拍脑袋定的,而是实测数据给出的"可行性较高"取值:
| 实测点 | 结果 |
|---|---|
| 第 3 轮:152.9K | ✅ 通过(实测能力上界) |
| 第 3 轮:196K | ❌ 挂死 |
| 第 6 轮:126,893 | ✅ 冷启动唯一文本通过 |
| 第 6 轮:131,072(声明上限) | 扫描至上限无悬崖 |
| 跳档测试 +15.6K / +28K / +38K | 均正常,无状态依赖挂死 |
取 131072 的三条依据:
- 贴着实测上界、留足余量:131072 < 实测通过的 152.9K,留了约 15% 余量,保证声明值永远不会越过真实能力边界;
- 全区间验证无悬崖:最终配置下从 20K 一路扫到 131072,冷启动唯一文本全部通过,跳档挂死也已消除——声明范围内任何一个尺寸都是被实测覆盖过的;
- 超限行为可控:即便业务真送来超长请求,也是 0.8 秒快速返回 400;120K 大提示词 prefill 期间,2 token 小请求仍 1.59 秒返回,大上下文不牺牲在线请求。
对比第 5 轮的教训(声明 262144、真实能力只有十几 K,一个超限请求锁死整个服务 36 分钟),声明值宁可保守。131072 正是"够用"与"被验证"之间的那个平衡点:往下损的是业务空间,往上添的是挂死风险。
四、最终最优启动参数(可直接用)
#!/bin/bash
# GLM-5.3-Flash @ Atlas 800 A2 (8×Ascend 910B3) 实测最优配置
export OMP_PROC_BIND=false
export OMP_NUM_THREADS=128
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD
export HCCL_BUFFSIZE=1024
export HCCL_OP_EXPANSION_MODE="AIV"
export VLLM_ASCEND_ENABLE_FLASHCOMM1=1
export TASK_QUEUE_ENABLE=1
export VLLM_RPC_TIMEOUT=3600000
export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=3000
export HCCL_EXEC_TIMEOUT=3600
export HCCL_CONNECT_TIMEOUT=1200
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
vllm serve /root/.cache/GLM-5.3-Flash-w8a8 \
--host 0.0.0.0 \
--port 8077 \
--max-model-len 131072 \
--tensor-parallel-size 8 \
--enable-expert-parallel \
--seed 1024 \
--served-model-name glm5.3-flash \
--safetensors-load-strategy prefetch \
--max-num-seqs 16 \
--max-num-batched-tokens 8192 \
--trust-remote-code \
--tool-call-parser glm47 \
--reasoning-parser glm45 \
--enable-auto-tool-choice \
--quantization ascend \
--limit-mm-per-prompt '{"image":1,"video":0}' \
--gpu-memory-utilization 0.95 \
--enable-prefix-caching \
--async-scheduling \
--compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY","cudagraph_capture_sizes":[1,2,4,8,12,16]}' \
--additional-config '{
"ascend_compilation_config": {
"enable_npugraph_ex": true,
"enable_static_kernel": false
},
"enable_cpu_binding": true,
"enable_flashcomm1": true,
"multistream_overlap_shared_expert": true
}' \
--api-server-count 1
关键参数说明
| 参数 | 值 | 为什么 |
|---|---|---|
--max-num-batched-tokens | 8192 | 决定 KV 池大小的第一杠杆(32768 时 KV 池仅 4584) |
--speculative-config | 不加 | MTP 接受率仅 12%,换 3.4 倍上下文更划算 |
--max-model-len | 131072 | 略低于实测 152.9K,超限快速报 400 而非挂死(验证过程见 3.5) |
cudagraph_capture_sizes | [1,2,4,8,12,16] | 与 max-num-seqs=16 对齐,大于 16 的尺寸纯浪费显存 |
--enable-prefix-caching | 开 | 命中率 72.5%,TTFB 从 1.3s 降到 0.13~0.4s |
--gpu-memory-utilization | 0.95 | 多轮稳定,OOM 再退回 0.92 |
--max-num-seqs | 16 | 智能体场景并发低,减小调度压力 |
实测效果
| 指标 | 优化前 | 优化后 |
|---|---|---|
| KV 池 | 5356 | 8956(+67%) |
| 冷启动上下文 | ~13.4K | 126,893 ✅ |
| 跳档挂死 | +5K 即挂 | 消除 |
| 超限行为 | 无限挂死锁死服务 | 0.8s 返回 400 |
| 队头阻塞 | 小请求 90s 零响应 | 消除(120K prefill 中,小请求 1.59s 返回) |
| 生成速度 | 17.5 tok/s | 20.9 tok/s |
| 智能体端到端 | 挂死 | 6.43s,正确返回 tool_calls |
五、部署教程
5.1 下载模型
在宿主机准备目录,从 ModelScope 拉取昇腾 w8a8 量化版:
cd /mnt/sdb
mkdir GLM-5.3-Flash-w8a8
modelscope download --model Eco-Tech/GLM-5.3-Flash-w8a8 --local_dir ./
5.2 启动容器
用昇腾官方 vLLM-Ascend 镜像(已针对 GLM-5.3-Flash 适配),注意挂载全部 8 张卡:
docker run -d \
--name vllm-ascend-glm5.3 \
--net=host --shm-size=500g --privileged \
--device /dev/davinci0 --device /dev/davinci1 \
--device /dev/davinci2 --device /dev/davinci3 \
--device /dev/davinci4 --device /dev/davinci5 \
--device /dev/davinci6 --device /dev/davinci7 \
--device /dev/davinci_manager --device /dev/devmm_svm --device /dev/hisi_hdc \
-v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
-v /usr/local/Ascend/firmware:/usr/local/Ascend/firmware \
-v /usr/local/sbin/npu-smi:/usr/local/sbin/npu-smi \
-v /usr/local/sbin:/usr/local/sbin \
-v /etc/hccn.conf:/etc/hccn.conf:ro \
-v /mnt/sdb:/root/.cache \
-it quay.io/ascend/vllm-ascend:glm-5.3-flash bash
docker exec -it vllm-ascend-glm5.3 /bin/bash
模型目录
/mnt/sdb挂载到容器内/root/.cache,即启动参数里的
/root/.cache/GLM-5.3-Flash-w8a8。
5.3 环境检查
npu-smi info # 应看到 8 张卡,Health = OK
驱动、固件、CANN、PyTorch-Ascend 的版本必须严格配套,请以昇腾官方《版本配套表》为准——
这是最容易踩坑的一步,版本不匹配会引发各种难以定位的算子异常。
5.4 启动服务
chmod +x vllm-serve-glm53-final.sh
bash vllm-serve-glm53-final.sh
首次启动含权重加载 + 图编译,耗时较长,建议把 VLLM_RPC_TIMEOUT 设大(脚本中已设 3600000ms)。
5.5 四步验证
第 1 步:确认 KV 池
curl -s http://<host>:8077/metrics | grep cache_config_info
关注 num_gpu_blocks 和 block_size,两者相乘即 KV 池 token 数。如果远小于你的 max-model-len,就一定会有挂死。
第 2 步:确认调度健康
curl -s http://<host>:8077/metrics | grep -E "num_requests_running|num_requests_waiting|kv_cache_usage_perc"
正常空闲状态应全为 0。
第 3 步:工具调用链路
curl -s -X POST http://<host>:8077/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"glm5.3-flash","messages":[{"role":"user","content":"北京今天天气怎么样?"}],
"tools":[{"type":"function","function":{"name":"get_weather","description":"获取城市天气",
"parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}}],
"tool_choice":"auto","stream":false,"max_tokens":256}'
应正确返回 tool_calls 且 finish_reason=tool_calls。
第 4 步:长上下文冷启动验证
这一步最容易被跳过,也最容易埋雷。务必用唯一文本(不要用重复段落,否则前缀缓存会让结果虚高 3.8 倍)。
本文所有数据均来自 Atlas 800 A2 实机实测,配置与测试方法可复现。欢迎在评论区交流你在昇腾部署上遇到的问题。
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐
所有评论(0)