AI ChatGLM实践笔记
服务器是 24GB 显存的 RTX 3090,这个配置跑大的模型会比较吃紧,所以从一开始就要在模型选择和优化上下功夫。
Python 环境用的是 3.10,PyTorch 2.0.1,CUDA 11.8。
为什么折腾 ChatGLM
出发点其实很简单:想在自己的服务器上跑一个能用的对话模型。
选 ChatGLM 主要有三个原因:
- 国产模型,中文对话体验相对较好
- 开源版本对硬件要求相对合理
- 智谱提供了比较完整的生态和工具链
但我当时忽略了一个现实问题:硬件资源有限。服务器是 24GB 显存的 RTX 3090,这个配置跑大的模型会比较吃紧,所以从一开始就要在模型选择和优化上下功夫。
环境准备和模型选择
先说环境,这步其实没什么花样,但有几个版本坑需要注意。
Python 环境用的是 3.10,PyTorch 2.0.1,CUDA 11.8。这个组合相对稳定,但不要盲目升级 PyTorch 版本,ChatGLM 的模型加载代码对某些版本兼容性不好。
模型选择上,我试了几个版本:
- ChatGLM-6B:轻量,对话能力一般
- ChatGLM2-6B:性能提升,但显存占用增加
- GLM-4-9B:体验最好,但需要更精细的量化
最终选了 GLM-4-9B 的 4-bit 量化版本,在性能和资源之间取了个平衡。
模型下载可以直接从 Hugging Face 拉,或者用 ModelScope 镜像。国内环境建议后者:
pip install modelscope
python -c "from modelscope import snapshot_download; snapshot_download('ZhipuAI/glm-4-9b-chat')"
基础对话实现
最基础的对话实现其实不复杂,用 Hugging Face 的 Transformers 库就能搞定:
from transformers import AutoTokenizer, AutoModelForCausalLM
model_path = "ZhipuAI/glm-4-9b-chat"
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_path,
trust_remote_code=True,
device_map="auto",
load_in_4bit=True
)
def chat(message, history=[]):
input_text = tokenizer.apply_chat_template(
history + [{"role": "user", "content": message}],
tokenize=False,
add_generation_prompt=True
)
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)
outputs = model.generate(**inputs, max_new_tokens=512, do_sample=True, temperature=0.7)
return tokenizer.decode(outputs[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True)
这段代码能跑起来,但有几个问题:
- 没有对话历史管理
- 没有流式输出,用户体验差
- 没有任何异常处理
工程实践里这些都不能少,但先让它跑起来再说。
踩坑记录
显存不够
第一个坑就是显存。3090 24GB 看起来不少,但加载 9B 模型还是紧张。
试过几个方案:
- 降低
max_new_tokens:从 512 降到 256,效果还行 - 调整
load_in_4bit参数:用 NF4 量化,节省了一些 - 批处理大小设为 1:避免中间状态累积
后来发现一个更实用的办法,用 device_map="auto" 让 Transformers 自动分配,比手动指定更可靠。
上下文管理
ChatGLM 的上下文格式和 OpenAI 不太一样。开始没注意这个问题,直接按 GPT 的格式传历史记录,结果回复质量明显下降。
正确做法是用 apply_chat_template 处理:
history = [
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好!有什么我可以帮你的吗?"}
]
# 错误方式:直接拼接
# correct_input = "\n".join([f"{msg['role']}: {msg['content']}" for msg in history])
# 正确方式:用 template
input_text = tokenizer.apply_chat_template(history, tokenize=False, add_generation_prompt=True)
生成参数调优
默认的生成参数往往不是最优的。temperature、top_p、top_k 这些参数需要根据实际场景调。
我的经验:
- temperature 0.7:平衡创造性和稳定性
- top_p 0.9:控制输出范围
- top_k 40:适当限制候选词
- repetition_penalty 1.1:减少重复
这些参数不是固定的,不同任务需要不同调优。
完整的对话服务
把前面的坑补齐后,整个对话服务的核心流程大概是这样:
实际代码里还要加异常处理、并发控制、超时机制这些工程化内容。比如:
from transformers import GenerationConfig
def chat_service(message, conversation_id, timeout=30):
try:
history = load_history(conversation_id)
history = history[-8:] # 限制历史长度
input_text = tokenizer.apply_chat_template(
history + [{"role": "user", "content": message}],
tokenize=False,
add_generation_prompt=True
)
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)
gen_config = GenerationConfig(
max_new_tokens=512,
temperature=0.7,
top_p=0.9,
repetition_penalty=1.1,
do_sample=True
)
outputs = model.generate(**inputs, generation_config=gen_config)
response = tokenizer.decode(outputs[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True)
save_history(conversation_id, history + [{"role": "user", "content": message}, {"role": "assistant", "content": response}])
return response
except Exception as e:
logger.error(f"Chat failed: {e}")
return "抱歉,出了一点问题,请重试。"
性能优化
单纯跑起来不够,还要考虑性能。
几个有效的优化手段:
- 模型并行:如果有多 GPU,用
device_map="balanced" - KV Cache:减少重复计算
- 批处理:多个请求合并处理(如果延迟要求不高)
- 静态图:固定输入长度,减少内存分配
实测下来,KV Cache 和批处理效果最明显。单次响应从 3-4 秒降到了 1.5-2 秒。
结果和边界
折腾了一个多月,总算有了一个能用的对话服务。效果不算惊艳,但在特定场景下表现不错。
日常问答、代码解释、文本摘要这些任务都能胜任,但复杂推理和长文本生成还是会暴露问题。
几个明确的边界:
- 硬件资源限制了模型规模
- 量化版本在某些任务上损失明显
- 对话历史管理还是手工活
- 多轮对话的一致性需要额外处理
一些闲话
这次折腾最大的感受是:模型本身只是起点,真正要把对话体验做好,工程化细节比想象中重要。
从模型选择到参数调优,从上下文管理到性能优化,每个环节都有坑要踩。这些在论文和文档里往往一笔带过,但实际落地时必须认真对待。
ChatGLM 作为一个开源模型,已经做得不错了。但要用好它,还需要根据自己的场景和资源做很多定制。这可能是所有开源模型的共同特点。
最后提醒一句:别一上来就追求"完美方案"。先跑起来,再迭代。对话系统尤其如此,用户体验需要在实际使用中慢慢调优。
版权声明: 本文首发于 指尖魔法屋-AI ChatGLM实践笔记(https://blog.thinkmoon.cn/post/415-ai-chatglm-glm-chat-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。