8张A100部署GLM-5.2的折腾记录

记录一次在8张A100 80G上部署GLM-5.2的过程,从vLLM兼容性问题切换到llama.cpp和GGUF,并继续测试上下文、并发及推理速度

# 背景

最近在公司封闭内网环境有一台安装了8张A100 80G的服务器空出来,真好我能拿来玩一玩,于是准备在上面部署GLM-5.2,给封闭内网中的其他应用提供大模型API。

最初的的想法是:总共有640G显存,GLM-5.2原版大约需要1000G显存,找一个量化一半的版本差不多。但是实际折腾之后发现有各种坑。最后模型虽然成功运行了,但速度、上下文长度和并发数之间又有很多取舍。本文简单记录一下整个过程,以及最后的结果。

# 先确认模型到底有多大

根据GLM-5.2官方模型页面vLLM的GLM-5.2部署文档,GLM-5.2总参数约为753B,其中每个token激活约39B参数,原生支持1M上下文。

Unsloth提供的GGUF版本中,各量化版本的大致大小如下:

量化版本 大小 我的理解
BF16 1.51TB 超过640G显存肯定放不下
Q8_0 801GB 超过640G显存肯定放不下
UD-Q5_K_M 561GB 可能可以跑,但是没有其他缓存空间
UD-Q4_K_M 466GB 质量较稳,8卡可以尝试
UD-Q4_K_S 436GB 比Q4_K_M更省空间
UD-Q3_K_M 343GB 空间明显更宽裕,但质量损失更大

因此对这台服务器来说,Q4基本是质量和容量之间比较现实的选择。模型能放下之后剩余显存还需要留给KV Cache、CUDA计算缓冲区和框架本身,不能按640G全部用于权重来计算。

# 尝试使用vLLM

对于想要生产环境较稳定运行大模型,vLLM是首选,先基于这个研究。

# 官方镜像拉不到

一开始找到vLLM上GLM官方上传的Docker镜像

1
2
vllm/vllm-openai:glm52
vllm/vllm-openai:glm52-cu129

但是内网环境服务器网络条件复杂,拉不下来。

# NVFP4版本不能用于A100

然后我试了nvidia/GLM-5.2-NVFP4,起初觉得这个是FP4量化,和Q4量化占用的显存差不多。但下载之后发现报错:

1
2
3
ValueError: no valid attention backend found for cuda with AttentionSelectorConfig
reasons: kv_cache_dtype not supported, sparse not supported,
compute capability not supported, flashattention mla not supported on this device

这个错误不是模型路径有问题,而是vLLM在给GLM-5.2选择Attention后端时,发现当前A100不满足它所需的KV Cache类型、稀疏Attention、计算能力或FlashAttention MLA实现。

仔细研究后发现:模型的详情页面中明确写了支持的GPU微架构为NVIDIA Blackwell,主要面向B200、B300一类GPU。我用的A100属于Ampere架构,不支持Blackwell上的NVFP4计算路径。因此就算成功用vllm框架加载了模型,也没法运行。

# FP8版本也不适合这台服务器

随后又看了zai-org/GLM-5.2-FP8。FP8不是只支持Blackwell,但官方vLLM配方推荐的单机配置是8张141G显存的H20或H200,完整1M上下文则推荐更大的B200。用我现在的配置根本跑不起来。

# 没有适合8卡A100运行的vLLM模型

如果一定要在A100上使用vLLM,理论上比较合适的是Q4 AWQ或GPTQ的safetensors模型。既能降低权重大小,也符合vLLM常见的量化加载方式。

但当时搜索了一圈,没有找到来源可靠、可以直接加载的GLM-5.2 AWQ或GPTQ safetensors版本。最终决定放弃vLLM,转向llama.cpp的方式启动。

# 使用llama.cpp加载GGUF模型

# 下载模型

我最后选择的是Unsloth发布的UD-Q4_K_M版本。联网环境可以使用Hugging Face CLI下载:

1
2
3
hf download unsloth/GLM-5.2-GGUF \
  --include "UD-Q4_K_M/*" \
  --local-dir /data/models/GLM-5.2-GGUF

这是一个分片GGUF模型。下载完成后目录中会有多个文件,启动时只需要指向第一个分片,llama.cpp会自动加载其余分片。

1
2
3
4
5
/data/models/GLM-5.2-GGUF/
└── UD-Q4_K_M/
    ├── GLM-5.2-UD-Q4_K_M-00001-of-00011.gguf
    ├── GLM-5.2-UD-Q4_K_M-00002-of-00011.gguf
    └── ...

# 整理后的Docker启动命令

我最后使用的配置整理如下。模型路径需要根据自己的实际目录修改:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
docker run -d \
  --name glm52-q4-llamacpp \
  --restart unless-stopped \
  --gpus all \
  --ipc=host \
  --shm-size 64g \
  -p 8000:8000 \
  -v /data/models/GLM-5.2-GGUF:/models:ro \
  ghcr.io/ggml-org/llama.cpp:server-cuda \
  -m /models/UD-Q4_K_M/GLM-5.2-UD-Q4_K_M-00001-of-00011.gguf \
  --alias glm-5.2-q4 \
  --host 0.0.0.0 \
  --port 8000 \
  --n-gpu-layers all \
  --ctx-size 131072 \
  --parallel 2 \
  --split-mode row \
  --main-gpu 0 \
  --batch-size 8192 \
  --ubatch-size 8192 \
  --cache-type-k q8_0 \
  --cache-type-v q8_0 \
  --flash-attn on \
  --metrics

# AI解释参数和逻辑的环节

因为参数很长,我懒得打字了,这部分参数含义都是直接让AI总结的

# llama.cpp启动参数总结

参数 作用
-d 让容器在后台运行
--name 设置容器名称,方便查看日志和重启
--restart unless-stopped Docker重启后自动恢复容器,除非之前手动停止
--gpus all 将宿主机全部GPU提供给容器
--ipc=host 与宿主机共享IPC namespace和共享内存,适合多GPU进程通信
--shm-size 64g 设置容器私有/dev/shm上限;使用--ipc=host时通常实际共享的是宿主机/dev/shm,两者语义有些重复
-p 8000:8000 将宿主机8000端口映射到容器8000端口
-v ...:/models:ro 将宿主机模型目录只读挂载到容器的/models
-m 指定GGUF模型,分片模型指向第一个分片
--alias 设置OpenAI API中使用的模型名称
--host 0.0.0.0 监听所有网卡,否则容器外可能无法访问
--n-gpu-layers all 尽量将全部模型层加载到GPU,避免CPU参与权重推理
--ctx-size 设置共享KV Cache的总token容量
--parallel 设置server slot数量,也就是最多可以同时处理的请求数
--split-mode row 将权重按行切分到多张GPU并行计算
--main-gpu 0 row模式下指定中间结果及主要KV所在的GPU
--batch-size 逻辑最大batch,主要影响Prompt Prefill阶段
--ubatch-size 物理micro batch,控制每次实际送入计算图的token数量
--cache-type-k/v q8_0 将K和V两部分KV Cache从默认高精度压缩为Q8,节省显存
--flash-attn on 启用Flash Attention实现
--metrics 开启Prometheus格式的/metrics接口

--shm-size 64g并不是给模型权重分配64G内存。它只控制Linux共享内存空间,主要用于进程间通信。模型权重、普通内存和GPU显存都不从这里分配。

# Q4_K_S、Q4_K_M和Q3_K_M的选择

GGUF文件名中的Q4Q3表示权重降低后使用多少bit保存,K表示llama.cpp中的K-quant量化方案,后面的SM分别偏向Small和Medium。

量化版本 优点 缺点 适合情况
Q4_K_M 4bit中质量相对稳妥 文件更大,占用显存和带宽更多 优先保证回答质量
Q4_K_S 比Q4_K_M更小 质量略有下降 希望在质量变化不大时省一些资源
Q3_K_M 文件和显存占用明显降低 质量损失比Q4更明显 当前速度或容量完全不够时继续尝试

降低权重bit数可能让推理变快,主要不是因为GPU计算的数字位数少了,而是因为每生成一个token,需要从显存读取的权重数据减少了。大模型推理尤其是单用户Decode阶段经常受显存带宽限制,权重越小,每次经过MoE专家时需要搬运的数据越少。

但量化也需要反量化计算,而且不同量化格式的CUDA kernel优化程度不同。因此文件更小不保证一定更快。例如IQ4_XSIQ4_NLQ4_K_S更小,但在NVIDIA GPU上不一定比K-quant快。

# 上下文长度和并发数的关系

llama.cpp的--ctx-size容易被理解为“每个对话的上下文长度”,实际上它更接近server中共享KV Cache可以保存的总token数量。--parallel设置slot数量,也就是最多允许多少个请求同时占用这个KV Cache。

在多个slot都接近满上下文的保守情况下,可以按下面的公式规划:

1
2
每路可用上下文 ≈ ctx-size / parallel
总ctx-size ≈ 每路目标上下文 × parallel

例如:

参数 满载时每路上下文约为
--ctx-size 131072 --parallel 2 65536 tokens
--ctx-size 131072 --parallel 4 32768 tokens
--ctx-size 262144 --parallel 4 65536 tokens
--ctx-size 524288 --parallel 4 131072 tokens

不同版本的llama.cpp对统一KV Cache和slot分配方式一直在调整。空闲slot存在时,实际分配可能比简单除法灵活。因此最准确的结果应查看启动日志中的n_ctxn_ctx_per_seqn_ctx_slot,并使用接近生产环境的并发请求验证。上面的公式适合做最坏情况容量规划。

GLM-5.2原生支持1M上下文。如果希望4个并发都能同时用满1M,理论总容量是:

1
1048576 × 4 = 4194304

也就是--ctx-size 4194304 --parallel 4。但这只是数学关系,不代表当前机器可以启动。我的Q4_K_M权重加ctx-size 131072时每张A100已经使用约64G显存,剩余空间无法把KV Cache再扩大32倍。因此8张A100上“4个并发同时各用1M”基本不现实。

更合理的方式是逐步扩大并观察显存:

  1. --ctx-size 262144 --parallel 4,满载时每路约64K
  2. --ctx-size 524288 --parallel 4,满载时每路约128K
  3. OOM时先降低KV Cache精度,或者减少并发及每路上下文

需要注意,上下文包括输入Prompt、历史对话和本次生成内容,不是只计算用户输入。

# 测试token输出速度的方法

llama-server在非流式响应中会返回timings。其中:

  1. prompt_per_second是输入Prompt的Prefill速度
  2. predicted_per_second是生成阶段的输出速度
  3. predicted_n是本次生成的token数量

可以直接使用下面的命令查看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
curl -s http://127.0.0.1:8000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "glm-5.2-q4",
    "messages": [
      {"role": "user", "content": "请详细解释TCP三次握手的过程"}
    ],
    "stream": false,
    "max_tokens": 512
  }' | jq '{usage, timings}'

# 实际调参过程

# 第一次稳定配置

模型跑通后,我先将参数提高到:

1
2
3
4
--ctx-size 131072
--parallel 4
--cache-type-k q8_0
--cache-type-v q8_0

这时每张A100使用约60G显存,运行稳定,生成速度大约20token每秒。但按满载计算,每个slot只有约32K上下文。

# 第二次调整

随后为了测试更长的单路上下文及速度,我改为:

1
2
3
4
5
--split-mode row
--ctx-size 131072
--parallel 2
--batch-size 8192
--ubatch-size 8192

此时每张卡显存约64G,单路上下文约64K。但是生成速度降低到约15token每秒,推理时每张GPU利用率大约20%。问AI,了解到:15 tokens/s和20%利用率更可能是下面几个因素共同造成的:

  1. 这是一个753B参数的MoE模型,即使每个token只激活部分专家,权重读取量依然很大
  2. row模式每一层都需要多卡协同,GPU之间的同步及通信会限制速度
  3. GGUF量化kernel在A100上的效率不一定足够高,可能和vLLM专用FP8、AWQ kernel有一定差距

但是经过我不断调整各种参数,推理的输出速度总是在15-20token/s,无法提高。我问AI各种方案并试用也无法提升最终输出速度。于是决定继续研究。

# A100 GPU的不足

最后我翻阅网上资料,有人用4卡H20部署GLM5.2的例子 (https://fast.v2ex.com/t/1223460),提到了一些技巧:使用FP8 KV Cache、开启MTP speculative decoding、改用AWQ,并通过内存swap给KV Cache做tiering。4卡H20服务器使用llama.cpp的输出速度能达到大约20-30token每秒。

但是我记得H20的算力实际上没有A100高,但是这个例子中他实际获得的输出速度却比我用A100服务器快很多,为什么?

最后我随便搜索了一些资料:

总体来说,H20 并不是计算能力比 A100 更强,而是在大模型生成文字的 Decode(解码)阶段,性能瓶颈主要是显存带宽。每生成一个 Token,GPU 都需要重新读取大量模型权重,同时不断读写持续增长的 KV Cache。

H20 的 FP16 算力不到 A100 的一半,但显存带宽约为 4.0 TB/s;A100 80GB SXM 的显存带宽约为 2.039 TB/s。H20 的显存带宽接近 A100 的两倍,因此在长文本生成、大 Batch、追求整体 Tokens/s 的推理场景中,H20 可以明显快于 A100。文章中最终测得 H20 平均吞吐量约为 A100 的 1.8 倍,这与两者约 1.96 倍的显存带宽差距高度吻合。

回到我的情况,别人例子中4卡H20可以有约20-30token每秒的速度,而我的8卡A100的每张GPU利用率低且输出速度约为15-20token每秒,也符合这个显存带宽差距的例子。

# 总结

在8张A100 80G上使用llama.cpp和UD-Q4_K_M GGUF成功启动了GLM-5.2,并完成了实际对话。最终状态:每张GPU占用60-70G显存,单请求生成速度约20 tokens/s。

几个感想和结论:

  1. A100因为架构和显存带宽,更适合训练,作为推理卡的性价比低。
  2. 本地部署完整大模型的成本实在太高,普通人和普通公司基本搞不定。
  3. AI太好用了,部署的时候碰到什么不会的或者奇怪问题都可以问AI。甚至这个笔记的初稿也是AI写的。

# 参考

使用 Hugo 构建
主题 StackJimmy 设计