ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

云服务器部署ChatGLM3-6B全流程:从环境配置到API服务与显存优化

云服务器部署ChatGLM3-6B全流程:从环境配置到API服务与显存优化

大模型私有化部署这件事,从去年火到现在,问得最多的一类问题就是“我手上只有一台云主机,能不能把 ChatGLM3 跑起来”。答案是可以,而且比很多人想象的要简单,但“能跑起来”和“跑得舒服、跑得稳、跑得省钱”之间隔着不少细节。我自己前前后后在云上折腾过好几轮 ChatGLM3 的部署,从最初被显存和依赖版本按在地上摩擦,到后来能把整套流程压缩到半小时内搞定,中间踩的坑足够写一篇长文。这篇就把在云服务器上部署 ChatGLM3 的完整链路拆开讲清楚:选什么配置、环境怎么配、模型怎么拉、服务怎么起、接口怎么调、显存怎么省、出问题怎么查。不管你是想给自己搭一个随时能问的私人助手,还是想给团队做一个内部知识问答的原型,这套流程都能直接照着复现。

1. 先把账算清楚:部署 ChatGLM3 到底需要什么

很多人一上来就开机器、装环境,结果跑到一半发现显存不够或者磁盘爆了,又得推倒重来。部署大模型和部署一个普通 Web 服务最大的区别在于,它对硬件资源的要求是“硬门槛”,不是靠调优能绕过去的。所以在动手之前,先把 ChatGLM3 的资源账算明白,能省掉大量返工。

1.1 ChatGLM3-6B 的显存账与精度选择

ChatGLM3 最常部署的是 6B 版本,参数量约 62 亿。显存占用的大头来自模型权重本身,而权重占多少,取决于你用什么精度加载。这里有个简单的估算逻辑:参数量乘以每个参数占用的字节数。FP16 精度下每个参数占 2 字节,62 亿参数就是约 12.4GB;INT8 量化后每参数约 1 字节,降到 6GB 出头;INT4 量化后每参数约 0.5 字节,压到 3.5GB 左右。但注意,这只是权重,实际运行还要加上 KV Cache(对话上下文缓存)和推理过程中的中间激活值,通常要再预留 2 到 4GB 的余量。

所以结论很直接:想跑 FP16 原精度,至少需要 16GB 显存的卡,比如 A10、V100 16G 这个级别;想用 INT8,8GB 显存起步,T4、A10 都能胜任;想用 INT4,6GB 显存也能勉强跑,但上下文一长就容易吃紧。如果你的机器根本没有 GPU,纯 CPU 推理也能跑,只是速度会慢到让人怀疑人生——6B 模型在普通 CPU 上生成一句话可能要几十秒,只适合做功能验证,不适合实际使用。

精度模式权重显存建议最低显存典型适用场景
FP16约 12.4GB16GB追求回答质量,有充足显卡资源
INT8约 6.2GB8GB质量与成本平衡,主流选择
INT4约 3.5GB6GB显存紧张,做轻量验证或低频使用
CPU 推理内存约 13GB16GB 内存仅功能验证,不追求响应速度

我个人的建议是,如果预算允许,直接上 16GB 显存的机器跑 FP16,体验最接近官方 Demo;如果预算敏感,INT8 是性价比最高的档位,回答质量下降肉眼可见地小,但显存直接砍半。INT4 我实测下来在长对话里偶尔会出现答非所问,短问答还行,看你能不能接受。

1.2 云主机规格的取舍:GPU 型号、磁盘与带宽

选云主机的时候,除了看显存,还有几个容易被忽略的点。第一是磁盘,ChatGLM3-6B 的权重文件下载下来大约 12GB 左右(FP16),加上 Python 环境、依赖包、系统本身,建议系统盘至少 50GB,数据盘再单独挂 100GB 放模型和日志,避免系统盘被撑爆导致机器异常。第二是带宽,从模型仓库拉 12GB 的文件,如果公网带宽只有 1Mbps,那得下到天荒地老,建议临时把带宽拉高或者用内网传输的方式把模型文件先传上去。

GPU 型号方面,常见的几类卡我都试过:T4 是性价比之选,16GB 显存跑 INT8 很稳,缺点是算力一般,首字延迟稍高;A10 算力强不少,24GB 显存跑 FP16 绰绰有余,适合有一定并发需求的场景;V100 老而弥坚,但部分云厂商已经下架。选卡的时候别只看显存数字,还要看卡的代际,同样 16GB,新卡的推理效率明显更高。

提示:开机器之前先确认所选地域和可用区有没有你想要的 GPU 规格,热门卡型经常缺货,提前查好库存能避免开好机器却挂不上卡的尴尬。

1.3 操作系统与驱动的前置确认

操作系统我一般选 Ubuntu 20.04 或 22.04,社区资料多,踩坑时容易搜到答案。开好机器后第一件事不是急着装 Python,而是确认 GPU 驱动和 CUDA 是否就绪。执行nvidia-smi,如果能正常输出显卡型号、显存和驱动版本,说明驱动层没问题;如果报“command not found”,说明驱动没装或者没装对。云厂商的 GPU 镜像通常预装了驱动,但版本可能偏旧,ChatGLM3 对 CUDA 版本有一定要求,建议 CUDA 11.7 及以上。

驱动版本和 CUDA 版本之间是配套关系,不是随便升。我的经验是,直接用云厂商提供的“GPU 优化镜像”,省去自己装驱动的麻烦,这类镜像一般已经把驱动、CUDA、cuDNN 都配好了。如果只能用基础镜像,那就老老实实按官方文档装驱动,装完重启一次再验证,别跳过重启这一步,很多“驱动装了但不生效”的问题都是没重启导致的。

2. 环境搭建:Python 环境与依赖的精确配置

环境配置是部署过程中最容易翻车的环节,没有之一。ChatGLM3 依赖的 transformers、torch、peft 等库版本之间耦合很紧,版本对不上就是各种 ImportError 和运行时错误。我见过太多人在这步耗掉一整天,所以这部分我会把版本约束和隔离手段讲透。

2.1 用 conda 做环境隔离,别污染系统 Python

系统自带的 Python 千万别直接拿来装依赖,一是版本可能不对,二是装乱了很难恢复。用 conda 建一个独立环境是最稳妥的做法。先装 Miniconda,然后创建一个指定 Python 版本的环境:

# 下载并安装 Miniconda(以 Linux x86_64 为例) wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh bash Miniconda3-latest-Linux-x86_64.sh -b -p $HOME/miniconda3 source $HOME/miniconda3/bin/activate # 创建 Python 3.10 环境 conda create -n chatglm3 python=3.10 -y conda activate chatglm3

为什么选 Python 3.10?因为它是目前大模型生态兼容性最好的版本区间,3.8 偏老有些新库不支持,3.11 及以上又可能遇到某些依赖还没适配。3.10 是甜点区,实测下来最省心。

2.2 依赖安装的顺序与版本锁定

依赖安装有个顺序讲究:先装 torch,再装 transformers 和其他库。因为 torch 的版本决定了 CUDA 支持情况,先把它定下来,后面的库才能正确匹配。ChatGLM3 官方推荐 torch 2.0 以上,我实测 2.1 到 2.3 都能跑,但建议锁一个具体版本避免意外。

# 安装 PyTorch(CUDA 11.8 版本,按你的 CUDA 版本调整) pip install torch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 \ --index-url https://download.pytorch.org/whl/cu118 # 安装 ChatGLM3 核心依赖 pip install transformers==4.40.2 pip install accelerate sentencepiece cpm_kernels pip install protobuf==3.20.3

这里有几个版本坑要特别提醒。protobuf必须锁在 3.20.x,4.x 版本会和 transformers 冲突,报一堆莫名其妙的序列化错误;transformers版本不能太新,太新了 ChatGLM3 的模型代码可能不兼容,4.40 左右是比较稳的区间;cpm_kernels是 ChatGLM 系列特有的依赖,装不上会导致模型加载失败,如果 pip 装不上可以尝试从源码编译。

注意:安装过程中如果遇到tokenizers相关的编译错误,多半是 Rust 环境缺失,先pip install setuptools-rust再重装即可。这类报错看着吓人,其实都是环境缺件,按提示补上就行。

2.3 验证环境是否真正可用

装完依赖别急着拉模型,先做一次环境自检,确认 torch 能识别到 GPU:

import torch print("CUDA 可用:", torch.cuda.is_available()) print("GPU 数量:", torch.cuda.device_count()) print("GPU 型号:", torch.cuda.get_device_name(0)) print("显存总量:", torch.cuda.get_device_properties(0).total_memory / 1024**3, "GB")

如果CUDA 可用输出 False,说明 torch 装成了 CPU 版本,或者 CUDA 版本不匹配,这时候别往下走,先把这个问题解决。我踩过一次坑,torch 装的是 CPU 版,结果模型加载时没报错,但推理慢得像蜗牛,排查了半天才发现是 torch 的问题。所以这一步的自检不能省。

3. 模型获取与加载:从下载到跑通第一句话

环境就绪后,接下来是把模型弄到本地并成功加载。这一步的核心是下载的稳定性和加载参数的配置,前者影响你能不能拿到文件,后者影响你能不能跑起来。

3.1 模型文件的下载方式与断点续传

ChatGLM3-6B 的权重托管在公开的模型仓库上,最直接的方式是用 git lfs 克隆,但 12GB 的文件用 git 拉容易中断,而且 lfs 对大文件的支持在某些网络环境下不稳定。我更推荐用 huggingface 的huggingface-cli工具做下载,它支持断点续传,中断了重跑命令会接着下:

pip install huggingface_hub huggingface-cli download THUDM/chatglm3-6b \ --local-dir ./chatglm3-6b \ --local-dir-use-symlinks False

如果下载速度慢,可以配置镜像源加速,具体方式是在命令前设置环境变量指向国内镜像。下载完成后检查目录,应该能看到pytorch_model.bin(或多个分片文件)、config.json、tokenizer.model等关键文件,缺一个都加载不了。我遇到过下载“看似完成”但实际某个分片损坏的情况,加载时报权重 shape 不匹配,重新下载对应分片才解决,所以下载完最好核对一下文件大小是否和仓库标注一致。

3.2 加载参数的关键选择:精度、设备与量化

加载模型时的几个参数直接决定了显存占用和运行效果,这里逐个说清楚。trust_remote_code=True是必须的,因为 ChatGLM3 的模型代码不在 transformers 主库里,需要从模型目录加载自定义代码;device指定用 GPU 还是 CPU;torch_dtype指定精度,torch.float16是 FP16,torch.int8是 INT8 量化。

from transformers import AutoModel, AutoTokenizer tokenizer = AutoTokenizer.from_pretrained( "./chatglm3-6b", trust_remote_code=True ) model = AutoModel.from_pretrained( "./chatglm3-6b", trust_remote_code=True, device_map="auto", # 自动分配设备 torch_dtype=torch.float16 # FP16 精度 ).eval() response, history = model.chat(tokenizer, "你好,介绍一下你自己", history=[]) print(response)

device_map="auto"让 accelerate 自动决定模型各层放在哪,单卡场景下就是全放 GPU,多卡场景下会自动切分。如果你的显存不够跑 FP16,把torch_dtype换成torch.int8或者用model.quantize(4)做 INT4 量化。量化操作要在加载后、推理前做,且量化后的模型不能再切回高精度,这点要注意。

3.3 第一次对话验证与常见加载报错

跑通第一句话是重要的里程碑。如果这一步成功,说明环境、模型、显存都没问题,后面就是工程化的事了。第一次对话建议用短问题,比如“你好”,观察响应速度和输出是否正常。如果加载阶段就报错,常见的有几类:OutOfMemoryError是显存不够,降精度或换大卡;KeyError或AttributeError多半是 transformers 版本不匹配;RuntimeError: expected scalar type是精度参数和模型权重不一致,检查torch_dtype设置。

我印象最深的一次是模型加载成功但推理时输出乱码,排查后发现是 tokenizer 加载路径和模型路径不一致,用了两个不同版本的 tokenizer。所以加载时 tokenizer 和 model 一定要指向同一个目录,别图省事混用。

4. 把模型变成服务:API 封装与接口调用

模型能在脚本里跑通,只是完成了“能用”,要真正投入使用,得把它封装成一个服务,让其他程序能通过接口调用。ChatGLM3 官方提供了 OpenAI 兼容的 API 服务脚本,这是最省事的路径。

4.1 启动官方 API 服务的完整命令

官方仓库里有个openai_api.py脚本,启动后就是一个兼容 OpenAI 接口规范的服务,意味着任何支持 OpenAI 接口的客户端都能直接对接。启动命令大致如下:

python openai_api.py \ --model-path ./chatglm3-6b \ --device cuda \ --precision fp16 \ --listen 0.0.0.0 \ --port 8000

--listen 0.0.0.0让服务监听所有网卡,这样从外部才能访问;--port指定端口,注意云主机的安全组要放行对应端口,否则本地能访问、外部访问不了。启动后服务会加载模型,加载完成会打印监听地址,这时候就可以用 curl 测试了。

4.2 用 curl 和 Python 客户端验证接口

先用最简单的 curl 确认服务活着:

curl http://127.0.0.1:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "chatglm3-6b", "messages": [{"role": "user", "content": "用一句话解释什么是机器学习"}], "temperature": 0.7 }'

如果返回了结构化的 JSON 和模型回答,说明服务正常。接着用 Python 的 openai 客户端库对接,这样后续集成到自己的应用里最方便:

from openai import OpenAI client = OpenAI( api_key="EMPTY", # 本地服务不校验 key,随便填 base_url="http://127.0.0.1:8000/v1" # 指向本地服务 ) resp = client.chat.completions.create( model="chatglm3-6b", messages=[{"role": "user", "content": "写一个 Python 冒泡排序"}] ) print(resp.choices[0].message.content)

api_key填任意非空字符串即可,因为本地服务默认不校验。base_url一定要带/v1后缀,这是 OpenAI 接口规范的要求,漏了会 404。

4.3 多轮对话与流式输出的处理

ChatGLM3 支持多轮对话,接口层面就是把历史消息按顺序放进messages数组,role 分别是 user 和 assistant 交替。流式输出则是在请求里加stream=True,服务会逐 token 返回,适合做打字机效果的前端。流式处理时要注意客户端要逐块读取并拼接,别一次性等全部返回,否则就失去流式的意义了。

多轮对话有个容易忽略的点:上下文长度是有上限的,ChatGLM3-6B 支持 8K 上下文,超过会被截断。如果你的应用场景对话很长,需要在客户端做历史裁剪,只保留最近若干轮,否则要么报错要么回答质量下降。我一般保留最近 10 轮左右,再往前的历史对当前回答的贡献已经很有限了。

5. 显存优化与性能调优:让有限的卡跑得更久

部署到生产环境后,很快会遇到“显存不够用”或者“响应太慢”的问题。这部分讲讲我实际用过的几种优化手段,从量化到并发控制,都是能直接落地的。

5.1 量化方案的实测对比与选择建议

前面提过 INT8 和 INT4 量化,这里补充实测感受。INT8 量化后显存从 12.4GB 降到 6GB 出头,回答质量下降很小,日常问答几乎感知不到差异,是我最推荐的方案。INT4 量化后显存降到 3.5GB 左右,但长文本生成时偶尔会出现逻辑断裂,短问答没问题。量化的代价是推理速度会略慢,因为要做反量化计算,但相比显存节省带来的收益,这点速度损失可以接受。

量化方式显存占用质量损失推理速度推荐场景
FP16约 12.4GB无最快资源充足,追求质量
INT8约 6.2GB极小略慢主流生产环境
INT4约 3.5GB可感知较慢显存极度紧张

5.2 并发请求下的显存与响应平衡

单请求跑通不代表能扛并发。多个请求同时进来时,每个请求都要占一份 KV Cache,显存会快速上涨。控制并发的手段有几个:一是限制服务端的最大并发数,官方脚本支持通过参数控制;二是给每个请求设置 max_tokens 上限,避免单个请求生成过长内容占满显存;三是在前面加一层队列,把请求排队处理,牺牲一点延迟换稳定。

我实测下来,16GB 显存的卡跑 INT8,同时处理 3 到 5 个中等长度的请求比较稳,再多就容易 OOM。如果你的业务并发高,要么加卡,要么用队列削峰,别指望单卡硬扛。

5.3 首字延迟与生成速度的优化思路

首字延迟主要受模型加载和首次推理影响,服务启动后的第一次请求会明显慢,因为要做各种初始化,之后会快很多。生成速度则和 GPU 算力、batch size 有关。优化手段包括:用更快的卡、降低精度、减少 max_tokens、开启 KV Cache 复用。另外,把模型常驻内存(服务启动时加载一次,而不是每次请求加载)是基本要求,官方脚本默认就是这么做的。

6. 踩坑实录:那些让我熬夜的报错与解决路径

部署过程中遇到的坑,才是真正值钱的经验。这部分我把印象最深的几个问题按排查链路完整还原,你遇到类似报错时可以照着思路走。

6.1 显存明明够却报 OOM 的排查过程

有次用 16GB 的卡跑 FP16,理论上 12.4GB 权重加缓存应该够,但一加载就 OOM。排查链路是这样的:先nvidia-smi看显存占用,发现加载前就有其他进程占了几个 GB,是之前没清理干净的残留进程;杀掉残留进程后还是 OOM,再查发现device_map="auto"把部分层放到了 CPU 又来回搬运,导致峰值显存超标;最后改成显式指定全部放 GPU,并调小max_length,问题解决。这个案例说明,OOM 不一定是显存真不够,可能是被其他进程占了,或者加载策略不合理。

6.2 依赖版本冲突的定位方法

依赖冲突的典型表现是 ImportError 或者运行时 AttributeError。定位方法是看报错栈里涉及哪个库,然后pip show 库名看当前版本,对照官方要求的版本区间。我遇到过一次transformers版本太新导致AutoModel.from_pretrained报参数不识别,降版本后解决。还有一次是accelerate版本和 torch 不匹配,导致 device_map 失效,也是降版本搞定。经验是:大模型部署的依赖版本,宁可锁死也别追新,官方推荐的组合是最稳的。

6.3 服务启动成功但外部访问不通的排查

这个坑很常见:本地 curl 通,外部访问不通。排查顺序是:先确认服务监听的是0.0.0.0而不是127.0.0.1;再确认云主机安全组放行了对应端口;然后确认系统防火墙(如 ufw)没拦截;最后确认客户端请求的地址和端口没写错。我踩过一次是安全组没放行,折腾了半天以为是服务问题,其实是网络层的事。所以遇到访问不通,先分层排查,别一上来就怀疑代码。

7. 从能跑到好用:几个提升体验的工程细节

服务跑起来只是起点,真正投入使用还要考虑稳定性、日志、监控这些工程细节。这部分分享几个我实际加上去之后体验明显提升的点。

7.1 用 systemd 托管服务实现开机自启

手动python openai_api.py启动的服务,终端一关就没了,机器重启也不会自动拉起。用 systemd 托管是标准做法,写一个 service 文件,配置好启动命令、工作目录、重启策略,就能实现开机自启和异常自动重启。关键配置包括Restart=always保证崩溃后自动拉起,WorkingDirectory指定模型目录,ExecStart写完整启动命令。配好之后systemctl enable加systemctl start,服务就托管给系统了。

7.2 日志留存与请求监控的最小方案

官方脚本的日志输出到标准输出,用 systemd 托管后可以用journalctl查看。但要做请求级别的监控,建议在服务前面加一层简单的反向代理(如 nginx),记录每个请求的耗时和状态码。这样既能做访问日志,又能顺便做限流和 HTTPS 终止。最小方案就是 nginx 反代加访问日志,够用且不复杂。

7.3 模型更新与回滚的平滑处理

模型文件更新时,直接覆盖正在使用的文件会导致服务异常。稳妥的做法是新模型下载到新目录,改配置指向新目录,重启服务,验证没问题后再删旧目录。如果新模型有问题,改回旧目录重启即可回滚。这个流程看着笨,但比“原地覆盖然后祈祷”可靠得多。

8. 成本控制:云上跑模型的省钱心得

最后聊聊钱的事,毕竟云主机按小时计费,跑一天模型烧的钱不少。几个实用的省钱手段:一是按需启停,不用的时候关机,GPU 机器关机后计算资源不计费(存储仍计费),比一直开着省很多;二是选抢占式实例做实验,价格便宜但可能被回收,适合调试不适合生产;三是把模型文件放在对象存储里,机器销毁后模型还在,下次开新机器直接拉下来,省去重复下载;四是监控显存利用率,如果长期利用率很低,说明配置过剩,降一档规格更划算。

我自己现在的做法是,日常开发用按需实例,随用随开;做压测或者长时间跑任务时,提前算好时长选包月,单价更低。这套组合下来,成本能压到一直开着的一半以下。部署这件事,技术门槛其实没有想象中高,真正拉开差距的是对这些工程细节的把控,把每个环节的“为什么”想清楚,后面遇到问题才不会慌。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进