大模型私有化部署这件事,从去年火到现在,问的人一点没少。我前前后后帮几个团队在云上落地过不同规模的对话模型,踩的坑从显卡驱动版本对不上,到显存算错导致 OOM,再到端口安全组没开白折腾一下午,基本都经历了一遍。这次拿ChatGLM3在云服务器上做一次完整部署复盘,把从选机器到跑通接口的全流程摊开讲。ChatGLM3 是智谱开源的一系列中英双语对话模型,其中 6B 版本对硬件相对友好,支持工具调用、代码执行和多轮对话,适合做企业内部知识问答、客服辅助、文档摘要这类场景。这篇内容面向的是有一定 Linux 基础、想自己动手把模型跑起来的开发者和运维同学,纯小白也能跟着走,但需要你对命令行不陌生。我会把每一步为什么这么做讲清楚,参数怎么算、坑在哪里、怎么绕,都写进去,你照着抄基本能复现。
1. 部署前的整体设计与选型思路
动手之前先把方案想明白,比上来就敲命令重要得多。私有化部署一个大模型,本质上是三件事的平衡:算力够不够、钱花多少、维护省不省心。这三者互相拉扯,选型就是找那个你能接受的平衡点。
1.1 为什么选云服务器而不是本地机器
很多人第一反应是拿自己的开发机跑,我劝你先算笔账。ChatGLM3-6B 的权重文件大约 12GB 出头,FP16 精度下光加载模型就要占 12GB 以上显存,加上推理时的 KV Cache 和中间激活值,实际运行峰值显存会到 13 到 14GB。你手头如果是一张 8GB 显存的消费级卡,跑 FP16 直接没戏,只能上量化版本,而量化又会损失一部分效果。本地机器还有个隐性成本:电费、散热、噪音,以及机器一旦关机服务就断了。
云服务器的优势在于弹性。你可以在需要压测的时候临时升配,平时用低配跑着,按量付费或者包月都行。对于团队协作场景,云上的机器天然可以被多人访问,不用每人都配一台高显存机器。我一般建议:验证阶段用按量付费的 GPU 实例,跑通了再考虑包月或者长期占用,这样试错成本最低。
1.2 实例规格与显卡型号的取舍
选实例的核心是看显卡。ChatGLM3-6B 在 FP16 下建议至少 16GB 显存,这样留出余量给并发请求。常见的云上 GPU 卡有这么几档,我整理成表格方便对照:
| 显卡型号 | 显存 | 跑 ChatGLM3-6B FP16 | 跑 INT8 量化 | 适用场景 |
|---|---|---|---|---|
| T4 | 16GB | 勉强,单并发可以 | 流畅 | 小流量验证、个人测试 |
| V100 | 16GB/32GB | 16GB 单并发,32GB 可多并发 | 流畅 | 中小团队内部使用 |
| A10 | 24GB | 流畅,支持少量并发 | 很流畅 | 生产环境入门 |
| A100 | 40GB/80GB | 很流畅,高并发 | 极流畅 | 高并发生产 |
这里有个容易忽略的点:显存不是唯一指标,显存带宽同样关键。大模型推理是显存带宽密集型任务,同样 16GB 显存,T4 的带宽只有 V100 的三分之一左右,实际推理速度差距明显。我实测过同一段 200 字的生成任务,T4 上大概要 8 到 10 秒,V100 上 3 到 4 秒就出来了。所以如果预算允许,优先选带宽高的卡。
1.3 系统镜像与运行环境的规划
操作系统我一般选 Ubuntu 20.04 或 22.04,原因是生态最全,CUDA 驱动、Python 包、各种编译依赖的兼容性最好。CentOS 系不是不行,但很多新版本的 PyTorch 和 transformers 在旧 glibc 上会遇到编译问题,排查起来费时间。
运行环境上,我强烈建议用conda 或 venv 做环境隔离。大模型部署涉及 torch、transformers、accelerate、fastapi 等一堆包,版本之间耦合很紧,直接装在系统 Python 里,后面想换个模型版本就可能把环境搞乱。用独立环境,出问题直接删掉重建,干净利落。
还有一个决策点是要不要用 Docker。Docker 的好处是环境可复现、迁移方便,坏处是 GPU 透传需要额外配置 nvidia-container-toolkit,初次上手会多一层门槛。我的建议是:如果你只在一台机器上跑,先用裸环境把流程跑通,跑通之后再考虑容器化。上来就搞容器,出问题你分不清是容器配置错了还是模型本身的问题。
2. 环境搭建的核心细节与实操要点
环境搭建是整个部署里最琐碎、也最容易卡住的环节。我见过太多人卡在驱动版本和 CUDA 版本不匹配上,一卡就是半天。这一章把关键环节拆开讲,每个操作都说明意图。
2.1 显卡驱动与 CUDA 的版本对齐
云服务器买回来,第一件事是确认显卡驱动状态。登录后执行:
nvidia-smi如果这条命令能正常输出显卡信息表格,说明驱动已经装好了,云厂商的 GPU 镜像通常预装了驱动。表格右上角会显示CUDA Version,注意这个数字是驱动支持的最高 CUDA 版本,不是你当前安装的 CUDA 版本,很多人在这里理解错。
接下来要装 CUDA Toolkit 和 cuDNN。这里的原则是:PyTorch 版本决定 CUDA 版本,CUDA 版本不能超过驱动支持的上限。比如驱动显示支持到 CUDA 12.2,那你可以装 CUDA 11.8 或 12.1,但不能装 12.4。我一般选 CUDA 11.8,因为它的 PyTorch 生态最成熟,兼容性最好。
安装 CUDA Toolkit 的推荐方式是用官方 runfile,比 apt 源更可控:
wget https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux.run sudo sh cuda_11.8.0_520.61.05_linux.run运行安装器时,记得把驱动安装选项取消勾选,只装 Toolkit。因为驱动已经由云镜像装好了,重复装驱动容易把现有驱动搞坏,这个坑我踩过一次,重装驱动后机器直接起不来,只能重装系统。
装完后配置环境变量,写进~/.bashrc:
export PATH=/usr/local/cuda-11.8/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda-11.8/lib64:$LD_LIBRARY_PATH然后source ~/.bashrc生效,用nvcc -V验证,能看到版本号就对了。
2.2 Python 环境与依赖包的精确安装
创建 conda 环境,指定 Python 3.10,这个版本和主流大模型框架兼容性最好:
conda create -n chatglm3 python=3.10 -y conda activate chatglm3装 PyTorch 时,一定要去 PyTorch 官网查对应 CUDA 版本的安装命令,不要凭记忆敲。CUDA 11.8 对应的命令是:
pip install torch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 --index-url https://download.pytorch.org/whl/cu118装完验证 GPU 是否可用:
import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))输出True和显卡型号就说明环境通了。如果输出False,八成是 CUDA 版本和 torch 版本对不上,或者环境变量没生效,回头检查这两处。
接着装模型运行需要的包:
pip install transformers==4.37.2 accelerate==0.25.0 sentencepiece protobuf fastapi uvicorn sse-starlette这里transformers的版本要特别注意,ChatGLM3 对版本有要求,太新或太旧都可能报错。4.37 这个版本是我实测下来最稳的,再新的版本有时会遇到tokenizer加载异常。
2.3 模型权重的获取与存放规划
模型权重从开源模型仓库下载,6B 版本大约 12GB。下载方式有两种:用git lfs克隆,或者用huggingface-cli下载。我推荐后者,支持断点续传,网络不稳的时候不至于前功尽弃:
pip install huggingface_hub huggingface-cli download THUDM/chatglm3-6b --local-dir /data/models/chatglm3-6b存放路径建议放在数据盘而不是系统盘,系统盘通常只有几十 GB,模型加上后续的日志、缓存很容易撑满。挂载数据盘后建一个/data/models目录专门放模型,后面所有路径都指向这里,迁移的时候也方便。
注意:下载过程中如果中断,重新执行命令会从断点继续,不用删掉重来。但如果你手动改过目录里的文件,续传可能校验失败,这时候删掉对应文件重新下即可。
3. 模型加载与推理服务的完整实现
环境通了,接下来是让模型真正跑起来。这一步分两个层次:先写个脚本验证模型能加载、能对话,再把它包装成 HTTP 服务供外部调用。很多人跳过第一步直接上服务,结果服务起不来还分不清是模型问题还是接口问题。
3.1 最小验证脚本:先确认模型能跑
写一个最简单的加载脚本,验证模型和显存都没问题:
from transformers import AutoModel, AutoTokenizer import torch model_path = "/data/models/chatglm3-6b" tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) model = AutoModel.from_pretrained( model_path, trust_remote_code=True, torch_dtype=torch.float16, device_map="auto" ) model = model.eval() response, history = model.chat(tokenizer, "你好,介绍一下你自己", history=[]) print(response)几个关键参数解释一下。trust_remote_code=True是必须的,因为 ChatGLM3 的模型结构代码不在 transformers 主库里,需要加载模型自带的实现。torch_dtype=torch.float16指定半精度加载,显存占用减半,效果损失很小。device_map="auto"让 accelerate 自动分配显存,单卡场景下就是全放一张卡上。
第一次运行会看到模型加载进度,12GB 权重加载大概需要一到两分钟,取决于磁盘 IO 速度。加载完成后输出一句自我介绍,说明模型跑通了。如果这里报 OOM,说明显存不够,要么换大显存卡,要么改用 INT8 量化加载:
model = AutoModel.from_pretrained( model_path, trust_remote_code=True, device_map="auto", load_in_8bit=True )INT8 量化后显存占用降到 7GB 左右,T4 这种 16GB 卡跑起来就很轻松了,代价是生成质量有轻微下降,日常问答场景基本感知不到。
3.2 封装成 HTTP 服务的关键设计
验证通过后,用 FastAPI 把模型包装成服务。这里的设计要点是模型只加载一次,常驻内存,请求来了直接推理。千万不要每个请求都重新加载模型,那样每次都要等一两分钟,完全没法用。
核心服务代码结构如下:
from fastapi import FastAPI from pydantic import BaseModel from transformers import AutoModel, AutoTokenizer import torch import uvicorn app = FastAPI() model_path = "/data/models/chatglm3-6b" tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) model = AutoModel.from_pretrained( model_path, trust_remote_code=True, torch_dtype=torch.float16, device_map="auto" ).eval() class ChatRequest(BaseModel): prompt: str history: list = [] @app.post("/chat") def chat(req: ChatRequest): response, new_history = model.chat( tokenizer, req.prompt, history=req.history ) return {"response": response, "history": new_history} if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)这段代码有几个细节值得说。host="0.0.0.0"让服务监听所有网卡,否则只能本机访问。history参数用于多轮对话,前端把上一轮的 history 传回来,模型就能记住上下文。但要注意 history 会占用显存,长对话累积多了会 OOM,生产环境一般限制保留最近 5 到 10 轮。
启动服务:
python server.py看到Uvicorn running on http://0.0.0.0:8000就说明起来了。用 curl 测试:
curl -X POST http://127.0.0.1:8000/chat \ -H "Content-Type: application/json" \ -d '{"prompt": "用一句话解释什么是机器学习", "history": []}'能返回 JSON 结果就说明服务正常。
3.3 并发处理与性能调优的实操
单进程的 FastAPI 服务是串行处理请求的,一个请求在推理时,其他请求只能排队。对于内部小工具够用,但稍微有点并发就不行了。提升吞吐有几个方向:
第一,用多 worker 起多个进程。但每个进程都会加载一份模型,显存直接翻倍,所以这条路只在显存充裕时可行。比如 A100 80GB 可以起 2 到 3 个 worker。
第二,用 vLLM 这类推理加速框架。vLLM 通过 PagedAttention 技术管理 KV Cache,显存利用率高,吞吐能提升好几倍。代价是部署复杂度上升,需要额外适配。我一般建议流量稳定后再上 vLLM,验证阶段用原生 transformers 就够了。
第三,调整生成参数控制响应时间。model.chat支持max_length、temperature、top_p等参数。max_length限制生成的最大长度,设小一点响应更快;temperature控制随机性,做客服问答建议设 0.1 到 0.3,让回答更稳定。
response, history = model.chat( tokenizer, prompt, history=history, max_length=512, temperature=0.3, top_p=0.8 )实测下来,max_length=512在大多数问答场景够用,响应时间能控制在 5 秒以内。
4. 网络配置与安全加固的落地细节
服务在本机跑通只是第一步,要让团队其他人访问,还得处理网络和安全。这块是运维同学的主场,但开发者也得懂,不然服务起不来还以为是代码问题。
4.1 安全组与端口放行的正确姿势
云服务器的安全组相当于一道防火墙,默认只开放少数端口。你的服务监听 8000 端口,但外部访问不了,八成是安全组没放行。去云控制台找到实例的安全组配置,添加入方向规则:
| 规则项 | 配置值 | 说明 |
|---|---|---|
| 协议类型 | TCP | HTTP 服务用 TCP |
| 端口范围 | 8000/8000 | 与服务监听端口一致 |
| 授权对象 | 指定 IP 段 | 只放行团队出口 IP,不要用 0.0.0.0/0 |
最后一条特别重要。千万不要图省事把授权对象设成 0.0.0.0/0,那等于把服务暴露给整个互联网,模型被人白嫖是小事,万一被用来跑恶意内容,责任算你的。正确做法是只放行公司或团队的出口 IP 段,个人测试就放行自己家的 IP。
4.2 用 Nginx 做反向代理与访问控制
直接暴露 uvicorn 的 8000 端口不够优雅,也不安全。标准做法是前面挂一层 Nginx,做反向代理、加访问日志、配 HTTPS。Nginx 配置大概长这样:
server { listen 80; server_name your-domain.com; location /chat { proxy_pass http://127.0.0.1:8000/chat; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 120s; } }proxy_read_timeout要调大,因为大模型生成响应可能超过 Nginx 默认的 60 秒,超时了会返回 504,让人误以为服务挂了。我一般设 120 秒,留足余量。
如果要对内网做简单鉴权,可以在 Nginx 层加 Basic Auth:
sudo apt install apache2-utils htpasswd -c /etc/nginx/.htpasswd youruser然后在 location 块里加两行:
auth_basic "Restricted"; auth_basic_user_file /etc/nginx/.htpasswd;这样访问时需要输入用户名密码,虽然简陋但能挡住大部分误访问。
4.3 服务常驻与开机自启的配置
用python server.py前台启动,SSH 一断开服务就没了。生产环境要用 systemd 托管,实现后台运行和开机自启。创建服务文件/etc/systemd/system/chatglm.service:
[Unit] Description=ChatGLM3 Service After=network.target [Service] Type=simple User=youruser WorkingDirectory=/data/chatglm ExecStart=/home/youruser/miniconda3/envs/chatglm3/bin/python /data/chatglm/server.py Restart=always RestartSec=10 [Install] WantedBy=multi-user.target几个要点:ExecStart要用 conda 环境里 Python 的绝对路径,否则 systemd 找不到依赖;Restart=always让服务崩溃后自动拉起;RestartSec=10避免频繁重启。配置好后执行:
sudo systemctl daemon-reload sudo systemctl enable chatglm sudo systemctl start chatglm sudo systemctl status chatglm看到active (running)就说明托管成功了。以后改代码只需sudo systemctl restart chatglm,不用再手动起进程。
5. 常见问题排查与避坑经验实录
部署过程中遇到的问题,我整理成速查表,按现象、原因、解决思路排列,遇到问题先查表,能省不少时间。
5.1 高频报错速查表
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
torch.cuda.is_available()返回 False | CUDA 与 torch 版本不匹配 | 核对驱动支持的 CUDA 上限,重装对应版本 torch |
| 加载模型时报 OOM | 显存不足 | 改用 INT8 量化,或换大显存实例 |
trust_remote_code相关报错 | transformers 版本过新 | 降级到 4.37 左右版本 |
| 服务启动后外部访问不了 | 安全组未放行 | 检查入方向规则,确认端口和授权 IP |
| 请求返回 504 | Nginx 超时 | 调大proxy_read_timeout |
| 多轮对话后 OOM | history 累积过长 | 限制保留轮数,或定期清空 history |
| 生成速度极慢 | 显卡带宽低或用了 CPU | 确认device_map生效,检查是否误用 CPU 推理 |
5.2 几个我踩过的真实坑
坑一:磁盘空间不足导致模型下载失败。系统盘默认可能只有 40GB,模型 12GB 加上 pip 缓存、conda 环境,很快就满了。表现是下载到一半报No space left on device。解决办法是提前挂载数据盘,把模型和缓存都放数据盘,pip 缓存目录也改到数据盘:
export PIP_CACHE_DIR=/data/cache/pip坑二:多进程加载模型导致显存翻倍。有次为了提升并发,用uvicorn --workers 4起了四个进程,结果每个进程都加载一份模型,16GB 卡直接 OOM。后来改成单 worker,并发靠排队解决,或者上 vLLM。这个教训是:大模型服务的并发模型和普通 Web 服务不一样,不能简单加 worker。
坑三:history 没做长度限制,长对话把显存吃光。用户连续对话几十轮后,history 里累积的 token 越来越多,KV Cache 线性增长,最后 OOM。解决办法是在服务端限制 history 长度,超过阈值就截断最早的几轮:
MAX_HISTORY = 10 if len(req.history) > MAX_HISTORY * 2: req.history = req.history[-MAX_HISTORY * 2:]history 是成对的(用户问、模型答),所以乘 2。
5.3 性能监控与日常维护建议
服务上线后要能看状态。最基础的监控是看 GPU 利用率和显存占用:
watch -n 2 nvidia-smi每两秒刷新一次,观察推理时 GPU 利用率是否打满、显存是否稳定。如果显存持续上涨不回落,说明有内存泄漏,多半是 history 没清理。
日志方面,uvicorn 默认输出访问日志,建议重定向到文件并做轮转,避免日志把磁盘写满。在 systemd 配置里加:
StandardOutput=append:/data/logs/chatglm.log StandardError=append:/data/logs/chatglm-error.log再配个 logrotate 定期切割,就稳妥了。
提示:模型服务对温度敏感,云上 GPU 机器长时间高负载运行,如果机房散热不好,可能触发降频导致推理变慢。遇到速度突然下降,先看
nvidia-smi里的温度和功耗是否异常。
6. 从验证到生产的扩展思路
跑通单机服务只是起点,真正要用于业务,还有几件事值得提前规划。这一章聊聊扩展方向,给后续迭代留个路标。
6.1 量化与推理加速的进一步优化
前面提到 INT8 量化能降显存,如果还想进一步压缩,可以试 INT4 量化,显存能降到 4GB 左右,普通消费级卡都能跑。但 INT4 对生成质量的影响就比较明显了,事实性问答容易出现偏差,适合对精度要求不高的场景。
推理加速方面,vLLM 是当前比较成熟的选择,它把 KV Cache 分页管理,多个请求共享显存,吞吐提升明显。迁移到 vLLM 需要改一下加载方式,模型权重格式基本兼容,工作量不大。另一个方向是用 TensorRT-LLM 做编译优化,性能更好但适配成本高,适合对延迟极敏感的场景。
6.2 多实例与负载均衡的架构演进
单机扛不住的时候,自然的思路是横向扩展。起多台 GPU 实例,每台跑一个服务,前面用 Nginx 或云负载均衡做分发。这里要注意会话粘性问题:多轮对话的 history 存在客户端,每次请求带上完整 history,所以请求打到哪台机器都行,不需要粘性会话。这个设计让横向扩展变得简单,加机器就行。
如果 history 存在服务端,那就得考虑会话路由或者共享存储,复杂度上一个台阶。所以我一直建议 history 由客户端管理,服务端保持无状态,扩展性最好。
6.3 业务集成的接口设计建议
给业务方提供接口时,除了基础的/chat,建议再加一个流式输出接口。大模型生成是逐 token 的,流式返回能让用户边生成边看到内容,体验好很多。FastAPI 配合sse-starlette可以很方便地实现:
from sse_starlette.sse import EventSourceResponse @app.post("/chat_stream") async def chat_stream(req: ChatRequest): async def event_generator(): for response, history in model.stream_chat( tokenizer, req.prompt, history=req.history ): yield {"data": response} return EventSourceResponse(event_generator())前端用 EventSource 接收,就能实现打字机效果。这个接口在客服、写作辅助场景几乎是标配,值得提前做进去。
另外,接口层面建议加请求频率限制和输入长度校验,防止有人传超长文本把显存打爆。这些防护措施在内部工具阶段可能觉得多余,但一旦开放给更多人用,就是必需品。
整套流程走下来,从买机器到服务上线,顺利的话半天能搞定,卡在环境问题上可能要一两天。我的体会是,把环境版本对齐这件事做扎实,后面 80% 的坑都能避开。模型本身很成熟,出问题的地方往往在驱动、依赖、网络这些外围环节。另外,验证阶段别急着上复杂架构,先用最朴素的方式跑通,再逐步加优化,这样每一步出问题都好定位。这套部署方案我在几个不同规模的团队里都用过,6B 模型配 24GB 显存的卡是比较舒服的组合,既能跑 FP16 又留了并发余量,预算再紧就上 INT8 配 16GB 卡,也能撑起内部使用。