# 背景
最近在公司封闭内网环境有一台安装了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镜像
|
|
但是内网环境服务器网络条件复杂,拉不下来。
# NVFP4版本不能用于A100
然后我试了nvidia/GLM-5.2-NVFP4,起初觉得这个是FP4量化,和Q4量化占用的显存差不多。但下载之后发现报错:
|
|
这个错误不是模型路径有问题,而是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下载:
|
|
这是一个分片GGUF模型。下载完成后目录中会有多个文件,启动时只需要指向第一个分片,llama.cpp会自动加载其余分片。
|
|
# 整理后的Docker启动命令
我最后使用的配置整理如下。模型路径需要根据自己的实际目录修改:
|
|
# 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文件名中的Q4、Q3表示权重降低后使用多少bit保存,K表示llama.cpp中的K-quant量化方案,后面的S和M分别偏向Small和Medium。
| 量化版本 | 优点 | 缺点 | 适合情况 |
|---|---|---|---|
Q4_K_M |
4bit中质量相对稳妥 | 文件更大,占用显存和带宽更多 | 优先保证回答质量 |
Q4_K_S |
比Q4_K_M更小 | 质量略有下降 | 希望在质量变化不大时省一些资源 |
Q3_K_M |
文件和显存占用明显降低 | 质量损失比Q4更明显 | 当前速度或容量完全不够时继续尝试 |
降低权重bit数可能让推理变快,主要不是因为GPU计算的数字位数少了,而是因为每生成一个token,需要从显存读取的权重数据减少了。大模型推理尤其是单用户Decode阶段经常受显存带宽限制,权重越小,每次经过MoE专家时需要搬运的数据越少。
但量化也需要反量化计算,而且不同量化格式的CUDA kernel优化程度不同。因此文件更小不保证一定更快。例如IQ4_XS和IQ4_NL比Q4_K_S更小,但在NVIDIA GPU上不一定比K-quant快。
# 上下文长度和并发数的关系
llama.cpp的--ctx-size容易被理解为“每个对话的上下文长度”,实际上它更接近server中共享KV Cache可以保存的总token数量。--parallel设置slot数量,也就是最多允许多少个请求同时占用这个KV Cache。
在多个slot都接近满上下文的保守情况下,可以按下面的公式规划:
|
|
例如:
| 参数 | 满载时每路上下文约为 |
|---|---|
--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_ctx、n_ctx_per_seq和n_ctx_slot,并使用接近生产环境的并发请求验证。上面的公式适合做最坏情况容量规划。
GLM-5.2原生支持1M上下文。如果希望4个并发都能同时用满1M,理论总容量是:
|
|
也就是--ctx-size 4194304 --parallel 4。但这只是数学关系,不代表当前机器可以启动。我的Q4_K_M权重加ctx-size 131072时每张A100已经使用约64G显存,剩余空间无法把KV Cache再扩大32倍。因此8张A100上“4个并发同时各用1M”基本不现实。
更合理的方式是逐步扩大并观察显存:
--ctx-size 262144 --parallel 4,满载时每路约64K--ctx-size 524288 --parallel 4,满载时每路约128K- OOM时先降低KV Cache精度,或者减少并发及每路上下文
需要注意,上下文包括输入Prompt、历史对话和本次生成内容,不是只计算用户输入。
# 测试token输出速度的方法
llama-server在非流式响应中会返回timings。其中:
prompt_per_second是输入Prompt的Prefill速度predicted_per_second是生成阶段的输出速度predicted_n是本次生成的token数量
可以直接使用下面的命令查看:
|
|
# 实际调参过程
# 第一次稳定配置
模型跑通后,我先将参数提高到:
|
|
这时每张A100使用约60G显存,运行稳定,生成速度大约20token每秒。但按满载计算,每个slot只有约32K上下文。
# 第二次调整
随后为了测试更长的单路上下文及速度,我改为:
|
|
此时每张卡显存约64G,单路上下文约64K。但是生成速度降低到约15token每秒,推理时每张GPU利用率大约20%。问AI,了解到:15 tokens/s和20%利用率更可能是下面几个因素共同造成的:
- 这是一个753B参数的MoE模型,即使每个token只激活部分专家,权重读取量依然很大
row模式每一层都需要多卡协同,GPU之间的同步及通信会限制速度- 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。
几个感想和结论:
- A100因为架构和显存带宽,更适合训练,作为推理卡的性价比低。
- 本地部署完整大模型的成本实在太高,普通人和普通公司基本搞不定。
- AI太好用了,部署的时候碰到什么不会的或者奇怪问题都可以问AI。甚至这个笔记的初稿也是AI写的。