☰
ChatGLM私有化问答助理本地部署全流程与避坑指南
2026/10/29 15:39:39 网站建设 项目流程

简介:面向希望掌握私有化智能问答系统搭建的开发者与机器学习学习者的实战项目包,围绕 Gradio 交互界面、FAISS 向量检索与 Sentence-Transformers 文本嵌入三项核心技术,演示文件上传、知识库构建到自然语言问答的完整链路,并强调本地部署下的数据隐私保护与行业定制能力。包内共 18 个文件,约 336KB:Python 源码承载问答主流程并保留多个迭代版本,txt 记录配置项与执行命令,yaml 用于环境参数配置,另附 whl 依赖包与 sh 安装脚本以简化环境准备,docx、xlsx、pdf 作为知识库测试语料,pem 证书与静态资源目录支撑服务运行。已有 140 人学习下载。读者可获得可运行的私有化问答项目骨架、依赖清单与多版本代码对照,理清文档切分、向量化索引、相似度匹配到答案生成的实现细节,并据此替换自有文档,快速改造为客服、技术支持或内部知识检索场景的原型应用。

1. 本地部署私有化智能问答助理:最容易出错的时刻,是第一次启动脚本

在本地部署一套私有化智能问答助理,最容易出错的地方是第一次用 python 启动脚本的那一瞬间,而不是下载权重的时候:显存瞬间被占满,进程被系统直接杀掉,终端只剩一行 Killed。这类需求在机器学习落地里特别常见——拿一个 ChatGLM 这样的开源对话模型,配合一份示例工程,把问答能力搬进自己的内网,让文档、工单、内部知识不出机房就能被问。装得好,它就是一个随时能问的助理;装不好,你会花两天时间在 CUDA、torch、transformers 三个版本号之间反复试探。下面这套流程是我自己跑通过、也在别人的机器上复现过的顺序,从选版本一直写到服务托管和排错。

2. 环境清单与版本选型:ChatGLM 这套组合该配哪个 Python 和 CUDA

安装之前先把版本定下来,比装完再回滚省事得多。同一份权重,在一台机器上跑得好好的,换一台就 Killed 或者报半精度算子不支持,差别通常不在模型,而在 Python 小版本、torch 的 CUDA 编译版本、以及驱动版本这三者没对齐。这一章先把硬件基线摸清楚,再把版本矩阵确定下来,最后建一个隔离环境,后面所有安装动作都在这个环境里做,不会污染系统自带的 Python。

2.1 先确认三样东西:显存、磁盘、驱动上限

显存决定你能不能跑、跑什么精度;磁盘决定你解压权重时会不会中途失败;驱动版本决定你能装哪个 CUDA runtime 编译的 torch。三样都确认一遍再动手,能省掉一半的返工。

加载方式权重基础显存占用(约)常见可用显卡适用场景
FP16 全量12~13 GB24 GB 显存档单人问答、效果优先
INT8 量化8~9 GB12~16 GB 档小团队内网共用
INT4 量化5~6 GB8 GB 显存档显存紧张、可接受精度损失
纯 CPU 推理内存 20 GB 以上无独显流程验证、低频使用

表里是权重本身的基础占用,还没算上对话历史带来的 KV cache。上下文从 2k 拉到 8k,实际占用还会再涨几个 G,所以显卡余量最好留出 3 GB 以上,别刚好卡在边界上。

# 看显卡型号、显存总量、驱动版本,这三项决定后面装哪个 torch nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv # 权重落地盘至少留 30 GB:权重 12~13 GB + 环境 6~8 GB + 日志缓存 df -h /data # 纯 CPU 推理时看内存,16 GB 的机器基本跑不动 6B free -g

驱动版本的规则只有一条:高版本驱动可以跑低版本 CUDA runtime 编译的程序,反过来不行。所以先看驱动,再选 torch,不要凭印象装一个 cu121 的包到只支持 CUDA 11 的驱动上,那样 torch.cuda.is_available() 会直接返回 False,而且报错信息不会告诉你原因。

2.2 Python 3.10 加 CUDA 11.8 这套组合最省心

版本矩阵我一般按下面这张表固定下来。它不是唯一能跑的,但在示例工程上出问题的概率最低。

组件建议版本说明
操作系统Ubuntu 20.04 / 22.04内核较新,驱动安装省事
显卡驱动535 及以上能覆盖 CUDA 12.x,向下兼容 11.8
Python3.10.x3.11/3.12 部分依赖的预编译包还没跟上
CUDA runtime11.8 或 12.1必须和 torch 的编译版本一致
PyTorch2.1.x2.0 也能跑,2.2 以上建议先小样本验证
transformers4.40 左右太新会改接口,太旧不认新配置
sentencepiece最新稳定版分词器依赖,缺了会报找不到模型文件

有个常见误解要澄清:跑推理不需要在系统里装完整的 CUDA Toolkit,nvcc 不存在也完全不影响。torch 的预编译包里已经带了运行所需的 CUDA 运行库,只有你要自己编译算子的时候才需要 toolkit。所以nvcc --version报 command not found 时不要急着重装系统环境,先确认 torch 自己的版本对不对。

# 确认系统里有没有装过多个 python,避免 pip 装到别的解释器上 which -a python3 python3 -V # 如果系统里残留旧版 CUDA,看一下运行库路径是否被写进了环境变量 echo $LD_LIBRARY_PATH

LD_LIBRARY_PATH里如果挂着别的 CUDA 目录,torch 加载时会优先用那里的库,出现「版本明明对但就是报错」的情况。我一般会把这一行清空,让 torch 用自己包内的运行库。

2.3 用 conda 建一个只服务于这套问答助理的环境

系统自带的 Python 被发行版锁死,动它风险太大。用 conda 起一个独立环境,出问题直接删掉重建,是成本最低的做法。

# 建环境,Python 小版本写死,别用 python=3 conda create -n qa-desk python=3.10 -y # 激活并确认解释器路径落在 conda 目录下,而不是 /usr/bin/python3 conda activate qa-desk which python python -V # 升级 pip,老版本 pip 解析依赖时容易选到不兼容的轮子 python -m pip install --upgrade pip

conda activate之后which python必须指向 conda 环境目录,如果还指向/usr/bin/python3,说明 shell 没初始化 conda,后面所有 pip 安装都会装到系统环境里,这是新手最常犯的一个错误。执行conda init bash再重开终端可以修好。

环境建好后建议立刻固化一份依赖清单文件,哪怕现在还是空的:

# 记录环境快照,后面装崩了可以直接回滚 conda env export > environment.yml

提示:环境中不要混装 conda 渠道和 pip 渠道的同一个包,比如 conda 装了 numpy,pip 又装一遍,最后两个版本打架,报错信息会指向一个莫名其妙的 C 扩展。

3. 依赖与权重部署:从文件清单到第一次成功加载

环境准备好之后,就进入真正的部署环节。顺序很重要:先放权重、再装 torch、最后装模型侧的库,中间每一步都做一次独立验证。这样一旦出错,你能立刻知道是哪一层的问题,而不是对着一整屏报错猜。

3.1 权重目录该怎么摆

从发布方拿到的权重一般是一堆文件的集合,包含配置、建模代码、分词器和分片权重。目录结构错一层,加载时就会报找不到 config.json。

文件作用缺失后果
config.json模型结构配置直接报找不到配置文件
configuration_chatglm.py配置类定义trust_remote_code 加载失败
modeling_chatglm.py模型前向实现同上,报 ImportError
tokenization_chatglm.py分词器实现同上
tokenizer.model分词模型本体编码时报文件不存在
tokenizer_config.json / special_tokens_map.json分词器参数与特殊符号特殊 token 处理异常
pytorch_model-0000X-of-0000N.bin分片权重少一个就报找不到文件
pytorch_model.bin.index.json分片索引无法拼出完整权重

分片数量以实际发布为准,上表只说明每一类文件的作用,不要拿文件个数去对照。判断目录摆得对不对有个简单办法:看 config.json 是不是就在这一层。

# 权重统一放在独立数据盘,不要放在 home 目录下,后面迁移方便 mkdir -p /data/models/chatglm3-6b cd /data/models/chatglm3-6b # 列出文件并核对总大小,6B 模型的 FP16 权重在 12~13 GB 量级 ls -1 | sort du -sh . # 发布方给了校验清单就一定要跑,分片下载不完整是最隐蔽的坑 sha256sum -c checksums.sha256

校验这一步别省。断点续传中断过的分片,文件大小看起来是对的,加载时才会在某一层报张量维度不匹配,排查起来非常费时间。没有校验清单的,至少用du -sh和发布页标注的大小对一遍。

注意:解压之后很容易多套一层同名目录,导致 MODEL_PATH 指向了一个只有子目录的父目录。加载前ls一下,确认 config.json 和你敲的路径在同一层。

3.2 安装顺序:先 torch,再模型侧的库

这一步是整个部署里最容易出问题的地方。pip 解析依赖时,如果先装 transformers,它会顺手把 torch 当成普通依赖拉一个 CPU 版本下来;等你再装 CUDA 版本时,两个包互相覆盖,最后torch.cuda.is_available()返回 False,而屏幕上什么有用的报错都没有。

# 第一步:装和 CUDA 版本绑定的 torch,索引地址指向对应 CUDA 的编译版本 pip install torch==2.1.2 torchvision==0.16.2 \ --index-url https://download.pytorch.org/whl/cu118 # 第二步:立刻验证 torch 能不能看到卡,不通就不要往下走 python -c "import torch; print(torch.__version__, torch.cuda.is_available(), torch.cuda.get_device_name(0))" # 期望输出类似:2.1.2+cu118 True NVIDIA GeForce RTX 4090

torch.cuda.is_available()返回 True 才算过。如果返回 False,先看nvidia-smi能不能正常输出;能输出就说明是 torch 装成了 CPU 版,重装即可,不要浪费时间在驱动上。

# 第三步:装模型侧的库,版本写死,避免以后重装时漂移 pip install "transformers==4.40.2" "tokenizers==0.19.1" \ accelerate sentencepiece protobuf \ transformers_stream_generator cpm_kernels # 第四步:再验证一次 torch 没被覆盖 python -c "import torch, transformers; print(torch.__version__, transformers.__version__, torch.cuda.is_available())"

几个参数值得说明。transformers锁版本是因为它的接口在不同大版本之间改过,示例工程里的建模代码是按某个版本写的,装太新会报参数不认识。accelerate负责设备分配和权重加载的分片调度,缺了会在加载时提示安装。transformers_stream_generator和cpm_kernels是模型侧modeling文件里流式输出和自定义算子用到的,报缺包的时候基本就是这两个。

内网机器不通外网时,用离线方式把包搬过去:

# 在有网的机器上,把依赖和它们的依赖全部下载成轮子文件 pip download -d ./wheels -r requirements.txt # 打包后拷到目标机,用 --no-index 彻底断掉联网解析 pip install --no-index --find-links=./wheels -r requirements.txt

--no-index的意思是完全不走网络索引,--find-links指向本地目录。这两个参数一起用,能保证目标机装出来的版本和你在有网机器上验证过的完全一致。

3.3 用一段最小脚本确认模型能加载

依赖装完,先别急着起服务,用一段二十行的脚本验证加载和推理,这是整个流程的分界线。

# minimal_check.py:只做三件事——分词、加载、跑一轮对话 from transformers import AutoTokenizer, AutoModel MODEL_PATH = "/data/models/chatglm3-6b" # 指向含 config.json 的那一层 # trust_remote_code 是必需的,权重目录里的建模代码要被执行 tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True) # device="cuda" 让权重直接进显存;torch_dtype="auto" 读取 config 里的精度声明 model = AutoModel.from_pretrained( MODEL_PATH, trust_remote_code=True, device="cuda", torch_dtype="auto", ).eval() # eval 关掉 dropout,推理必须调用 response, history = model.chat(tokenizer, "用一句话说明什么是向量检索", history=[]) print(response) print("history rounds:", len(history) // 2)

trust_remote_code=True不是可选项,权重目录里的modeling_chatglm.py要通过它加载,关掉会直接报模型类型不认识。.eval()一定要调,否则推理结果会带随机性,同一句话问两遍答案不一样,你会误以为是模型问题。torch_dtype="auto"让精度跟随配置文件,想强制半精度就写torch.float16,但 CPU 上不能用半精度。

跑的过程中另开一个终端盯显存:

# 每秒刷新一次,观察加载峰值和推理后的常驻占用 nvidia-smi -l 1

正常情况下加载峰值会比常驻高 2 GB 左右,加载完回落到 12~13 GB(FP16)。如果常驻一直往上涨,多半是历史记录没做截断,后面配置章节会处理。

4. 服务化与参数配置:把模型包成内网可用的问答服务

脚本能跑通,说明环境是对的,但离「能用」还差一步:得让它在后台长期跑着,崩了自动拉起,请求有超时和鉴权,参数按业务场景调过。这一章做这件事。

4.1 四个真正影响体验的参数

参数不是越多越好,改下面四个基本够用,其余保持默认。

参数建议取值改动影响
加载精度FP16 或 INT4INT4 省显存但复杂推理会掉点
上下文长度2048 起步,按需到 8192拉长会线性增加显存和首 token 延迟
temperature0.7~0.95太低答案死板,太高开始胡编
top_p0.7~0.9控制候选词范围,配合 temperature 一起调
服务并发单进程单卡多进程会抢显存,建议排队而不是并行

关于并发有个误区要说清:6B 模型单次推理算力吃不满一张卡,但对话是自回归的,一个请求从头生成到尾都会占着显存,所以「一张卡跑多个 worker」的结果通常是 OOM 而不是吞吐翻倍。常见做法是一个进程配一个请求队列,或者在两张卡上各起一个实例。

# 生成参数在调用时传入,不用改模型代码 response, history = model.chat( tokenizer, "把下面这段话改写成三条要点:……", history=history, temperature=0.8, # 改写类任务偏高一点更自然,抽取类任务调低 top_p=0.8, max_length=2048, # 单轮输出上限,别设太大,长输出会拖慢整体响应 )

history要显式传回去才会带上下文,但每轮都会累积。业务上一般只保留最近 5~10 轮,超出就截断最早的两条,否则跑到几十轮之后显存和时间都会失控。

4.2 用 systemd 托管进程

前台脚本一关终端就没了,生产环境必须交给系统管。systemd 的好处是崩了自动拉起、日志统一走 journal、开机自启。

# /etc/systemd/system/qa-desk.service [Unit] Description=Local QA Assistant Service After=network.target [Service] Type=simple User=qa # 专用低权限账号,不要用 root WorkingDirectory=/opt/qa-desk Environment="CUDA_VISIBLE_DEVICES=0" # 多卡机器上指定用哪张 Environment="QA_TOKEN=change-me-in-env" # 鉴权口令走环境变量,别写进代码 ExecStart=/opt/conda/envs/qa-desk/bin/python /opt/qa-desk/serve.py Restart=always # 崩溃自动重启 RestartSec=5 # 间隔 5 秒,避免疯狂重启打满日志 LimitNOFILE=65535 [Install] WantedBy=multi-user.target

几个参数的含义:CUDA_VISIBLE_DEVICES决定进程能看到哪张卡,多卡机器上不指定会用 0 号卡,容易和别人的任务撞车。Restart=always配合RestartSec是保底措施,显存被别处占满导致启动失败时,它不会立刻无限重试。User用专用账号,避免服务进程有权限改系统文件。

# 改完 unit 文件必须 reload,否则 systemd 读的还是旧配置 systemctl daemon-reload systemctl enable --now qa-desk # 看状态和日志,-f 实时跟随 systemctl status qa-desk journalctl -u qa-desk -f -n 100

服务起不来时,journalctl里的第一行报错通常就是根因,后面的都是连锁反应。养成只看前 20 行的习惯,能少走很多弯路。

4.3 监听地址、鉴权与超时

服务监听的地址决定了它暴露给谁。内网私有化的基本要求是:只监听回环地址或者内网网段,前面再挡一层转发层做统一入口,不要把模型服务直接绑到 0.0.0.0 上对外。

# 只在回环地址监听,外部流量走统一的入口层进来 # serve.py 里对应的启动参数 # --host 127.0.0.1 --port 8000 ss -lntp | grep 8000 # 自测:带鉴权头调一次,观察返回结构和耗时 time curl -s http://127.0.0.1:8000/v1/chat \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $QA_TOKEN" \ -d '{"prompt":"用三句话说明什么是向量检索","history":[]}' | head -c 400

接口路径以示例工程里的路由为准,不同工程命名不一样,别照抄。

超时是最容易被忽略的一项。对话服务首 token 本来就慢,尤其第一次请求要预热显存和算子,接口层超时设在 10 秒基本必挂。我的习惯是首轮给 60 秒,流式输出情况下按空闲时间(两个 token 之间的间隔)判定超时,而不是按总时长,否则长回答一定被掐断。

注意:鉴权口令写在 unit 文件的 Environment 里,只对 root 可读。别把它硬编码进 Python 文件再提交到代码库。

5. 避坑与排查:安装环节最常见的五个出错点

下面五条都是我自己或者同事在实际部署里撞过的,按「现象 → 原因 → 解决」记,遇到对得上的直接照做。

5.1 启动瞬间进程消失,只剩 Killed

现象:执行加载脚本,进度条走到一半终端打印Killed,没有任何 Python 异常栈,journalctl里也只有一条 OOM 记录。

原因:系统内存或显存被吃穿。常见的是权重解压时占着内存、同时又用torch_dtype全精度加载,或者同卡还跑着别的任务。Linux 的 OOM killer 直接杀进程,所以看不到 Python 报错。

解决:先用nvidia-smi和free -g确认可用余量,再决定精度。显存不足就上 INT4,内存不足就先关掉别的进程、或者先把权重从压缩包解压到磁盘再从磁盘加载,别边解压边加载。判断是不是显存问题,加载时开一个nvidia-smi -l 1盯着,看峰值撞到哪。

5.2 报找不到某个模块,指向 modeling 文件

现象:ImportError: This modeling file requires the following packages that were not found in your environment: transformers_stream_generator,或者报cpm_kernels缺失。

原因:权重目录里的建模代码是运行时执行的,它自己会 import 一些可选依赖。这些包不是 transformers 的强依赖,pip 装 transformers 时不会带上。

解决:按报错里点名的包补装即可,pip install transformers_stream_generator cpm_kernels。装完不需要重装 transformers,重启进程就好。如果补装后还报同样的错,检查是不是装到了另一个 Python 环境里,which pip和which python要对得上。

5.3 CPU 上跑半精度报算子未实现

现象:RuntimeError: "LayerNormKernelImpl" not implemented for 'Half',通常发生在没卡或者强制指定 CPU 的机器上。

原因:半精度浮点的部分算子在 CPU 上没有实现,模型配置里默认的精度是半精度,CPU 加载时没做转换。

解决:CPU 场景显式改成全精度加载,torch_dtype=torch.float32,并把device设为cpu。顺带提醒,6B 模型纯 CPU 推理速度很慢,只适合验证链路,别指望它扛业务量。

5.4 分词结果全是特殊符号,答案完全跑偏

现象:模型能加载、能输出,但答非所问,或者输出的是一串重复的特殊 token。

原因:tokenizer.model没被正确读取,分词器退化成按字节处理,输入进模型的 ID 全是错的。多数是权重目录不完整,或者路径指到了父目录,分词器用了默认实现。

解决:确认tokenizer.model和tokenizer_config.json都在 MODEL_PATH 这一层,重新加载并打印一次编码结果验证:

ids = tokenizer.encode("你好") print(ids) # 应该是个位数的 token id,不是一长串 print(tokenizer.decode(ids)) # 应该能还原成原句

5.5 服务显示 active,但请求一直超时

现象:systemctl status是 active (running),ss -lntp能看到端口,但 curl 一直挂着不返回。

原因:三种可能——监听的地址是 127.0.0.1 而你在别的机器上调;模型还在加载权重(6B 从磁盘读要几十秒);或者接口层超时设得太短,首 token 还没出就被掐了。

解决:先在服务器本机 curl 一次,排除网络层;再看journalctl里有没有「模型加载完成」这类日志,加载期间接口是不响应的;最后把超时调到 60 秒以上,流式模式下改成按 token 间隔判定。

6. 装完怎么验证:三层检查确认问答链路真的通了

部署完成后,我一般按三层来验收,从下往上,每层都过了才算跑通。第一层是进程与资源,第二层是接口,第三层才看回答质量。跳过前两层直接看效果,很容易把环境问题误判成模型能力问题。

检查项命令或方法正常表现
进程状态systemctl is-active qa-desk输出 active
显存占用nvidia-smi12~13 GB 且不再持续上涨
端口监听ss -lntp | grep 8000只有 127.0.0.1 或内网地址
接口连通本机 curl 带鉴权头200,返回体里有文本字段
首 token 延迟time curl ...首次 1~3 秒,之后明显更快
长上下文灌 5000 字文本再提问不 OOM,回答不截断
并发两路两个终端同时 curl有排队但不崩

资源层重点看显存曲线而不是绝对值。刚启动时 12~13 GB 是正常的,但如果连续问十轮之后涨到 18 GB,那说明历史记录没截断,跑一天必然 OOM。这个问题在验收阶段发现,比上线后被业务方投诉好得多。

接口层验证建议固定一段脚本,不要每次手敲:

# verify.sh:一次性跑完接口层的三项检查 set -e BASE=http://127.0.0.1:8000 # 1) 健康检查,确认服务在响应而不是只在监听 curl -sf "$BASE/health" -o /dev/null && echo "health ok" # 2) 单轮问答,检查返回结构里有没有文本字段 curl -s "$BASE/v1/chat" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $QA_TOKEN" \ -d '{"prompt":"你好","history":[]}' | grep -q '"text"' && echo "chat ok" # 3) 长输入压测,确认上下文拉长后不崩 python - <<'PY' import json, urllib.request long_text = "这是一段用于压测的占位文本。" * 300 body = json.dumps({"prompt": "总结下面内容:" + long_text, "history": []}).encode() req = urllib.request.Request("http://127.0.0.1:8000/v1/chat", data=body, headers={"Content-Type": "application/json"}) print(urllib.request.urlopen(req, timeout=120).status) PY

set -e让脚本在任一步失败时立刻退出,避免前面挂了后面还在跑。第三步用标准库发请求而不是依赖第三方客户端,是为了让验证脚本在任何一台机器上都能直接跑。压测文本我习惯用固定的一句话重复 300 次,长度可控又能触发上下文边界。

质量层我一般只抽十条内部真实问题,覆盖三类:事实检索(问文档里写死的参数)、改写(把长句改成要点)、拒答(问知识库之外的内容)。前两类看准确度,第三类看它会不会硬编。第三类不通过,说明提示词模板或者上下文截断策略有问题,得回头调配置,跟安装本身无关了。

还有个小习惯是从那次显存泄漏的问题之后养成的:每次部署完,我会让服务空跑一个小时,期间用nvidia-smi --query-gpu=memory.used --format=csv -l 60记一行显存,一小时后对比首尾两行。数字没有缓慢爬升,我才认为这套本地私有化问答助理是真的装好了,而不是刚好在验收的十分钟里没出问题。后面再迁移到别的机器,我也是从环境基线表开始,一层层往上验,从不在第一次跑通就宣布完成。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询