资讯动态

GLM-4代码仓库本地部署:从权重加载到vLLM服务化与LoRA微调

发布时间:2026/11/6 4:38:26 来源:尧图企业网站定制
简介这份 GLM-4 代码仓库源码压缩包面向希望快速上手智谱 GLM-4 的开发者与学习者提供从基础对话、视觉理解到微调与部署验证的代码参考适合具备 Python 基础、想研究大模型推理服务和二次开发的中高级读者。包内共 78 个文件整体约 7.57MB以 Python 脚本、Markdown 说明、YAML/JSON 配置和少量图片、TypeScript 前端资源为主分别用于示例运行、参数配置、文档阅读与界面演示。目录围绕基础演示、微调示例、批处理与命令行工具、OpenAI API 服务、vLLM 调用、网页与视觉交互以及 Intel OpenVINO、ITREX 设备示例展开便于理解 GLM-4 的调用链路和工程组织。已有 307 人学习可作为研究源码结构、搭建本地推理服务与探索微调流程的实践素材。1. 拿到 glm4 代码仓库源码 zip 包先别急着 pip install下载完 glm4 代码仓库源码 zip 包很多人的第一反应是解压、进目录、pip install -r requirements.txt三连然后在 CUDA 版本冲突和 flash-attn 编译错误里耗掉半天最后连模型长什么样都没见到。这个包真正的价值不在“装上”而在于它把权重加载、tokenizer、chat template、推理脚本这几件事串成了一条能本地跑通的最小链路是一份可以改、可以嵌进自己业务、也可以当作微调起点的工程骨架。它适合三类人想在单卡上把 GLM-4 跑起来做效果验证的算法同学需要把它包成内部 API 的后端同学以及准备在它上面挂 LoRA 做领域适配的训练同学。下面的顺序就是我实际拆包的顺序从目录结构一路讲到服务化和回归验证。2. 解压与目录结构仓库里真正干活的只有这几个文件zip 包解开之后第一件事不是装依赖是把目录摸清楚。同一个仓库里通常混着三类东西能直接python xxx.py跑起来的示例脚本、被别的脚本 import 的库代码、以及一堆 CI 配置和文档。分不清这三类后面看报错就会完全失去方向——你以为是环境问题其实是跑错了入口文件。2.1 解压后的分层先找入口再看依赖我一般不解压到当前目录而是单独开一个工作目录把源码和权重分开放。原因是权重动辄十几 GB和源码混在一起之后du -sh、打包、传服务器都会变得很难受。# 源码和权重分两个目录后面所有脚本都靠这两个路径 mkdir -p ~/work/glm4 cd ~/work/glm4 unzip ~/Downloads/glm4-repo.zip -d ./src cd ./src # 只展开两层避免一屏刷出几千行 find . -maxdepth 2 -type d | sort # 找可以直接跑的入口带 __main__ 的脚本通常就是 demo grep -rl __main__ --include*.py . | head -20 # 依赖清单先看一眼别急着装 ls -la requirements*.txt setup.py pyproject.toml 2/dev/null这三条命令解决的是不同问题。find -maxdepth 2让你在一屏内看清仓库的顶层组织是一层src/包着所有代码还是examples/、training/、serving/平铺。grep -rl __main__是在找“可执行入口”这类文件才值得你花时间读其他的多半是被 import 的组件。最后那条ls是确认依赖声明到底在哪有的仓库把依赖拆成requirements.txt和requirements-dev.txt训练依赖单独放装错文件会出现“推理能跑、微调缺包”的怪现象。提示head -20不要省。有些仓库的examples/下有几十个脚本全部刷出来反而看不出重点。2.2 环境怎么定Python、torch、CUDA 的对齐顺序依赖装崩九成是顺序错了。正确的顺序是先看显卡驱动支持到什么程度再定 CUDA runtime再定 torch wheel最后才装仓库的 requirements。反过来做就会陷入“装了 torch 发现 CUDA 不匹配、卸了重装、结果 requirements 又把 torch 升回去”的死循环。# 1. 看驱动支持的上限右上角那个 CUDA Version 是上限不是当前版本 nvidia-smi # 2. 用独立虚拟环境别污染系统 Python python3 -m venv .venv source .venv/bin/activate python -V pip install --upgrade pip setuptools wheel # 3. 先单独装 torch装完立刻验证别等 requirements 一起装 pip install torch --index-url https://download.pytorch.org/whl/cu121 python -c import torch; print(torch.__version__, torch.cuda.is_available(), torch.cuda.device_count())torch.cuda.is_available()返回False是第一个必须当场解决的信号不要带着这个状态往下装。常见原因是虚拟环境里装的是 CPU 版 wheel或者宿主机驱动版本低于 wheel 要求的 CUDA runtime。确认能打印出True和卡数之后再执行仓库的pip install -r requirements.txt。如果 requirements 里再次出现 torch用pip install -r requirements.txt --no-deps之外更稳的做法是先看它锁的 torch 版本和你装的是否一致不一致就以能跑通的那份为准把 requirements 里那行注释掉。现象大概率原因处理方式torch.cuda.is_available()为 False装了 CPU 版 wheel / 驱动过低按驱动上限重装对应 cu 版本import torch 报 undefined symboltorch 与系统 CUDA 库混装卸载 conda 里的 cudatoolkit只用 pip wheel装 flash-attn 卡在编译半小时无预编译 wheel本机编译用对应版本预编译包或推理阶段先关掉2.3 权重与代码的对接代码只认目录不认 zip这一步是新手最容易翻车的地方。模型权重通常也是压缩包形式分发但加载代码需要的不是那个压缩包而是解压后的目录目录里必须有config.json、tokenizer_config.json、model.safetensors.index.json和若干分片文件。如果你把 zip 路径直接传进去报错信息往往是一句含糊的“找不到 config”看不出是路径问题。# 权重单独解压确认关键文件齐全 mkdir -p ~/work/glm4/weights unzip ~/Downloads/glm4-weights.zip -d ~/work/glm4/weights ls ~/work/glm4/weights | head # 分片文件应该和 index.json 里登记的列表对得上 python -c import json,os d$HOME/work/glm4/weights idxjson.load(open(os.path.join(d,model.safetensors.index.json))) missing[f for f in set(idx[weight_map].values()) if not os.path.exists(os.path.join(d,f))] print(缺失分片:, missing or 无) 最后那段小脚本值得留着。分片下载不完整是极常见的静默故障文件在名字对但大小是 0 或者只下了一半加载时报的是“unexpected end of file”之类的底层错误很难往“文件没下全”上想。先用 index.json 里的weight_map反查一遍能把这类问题挡在推理之前。权重目录和源码目录分开之后后面换模型、换量化版本都只是改一个路径常量的事。3. 把仓库跑起来单卡推理脚本的三段式改造仓库自带的 demo 通常只覆盖最理想的一条路径输入一句话、打印一段输出。要把它变成自己能用东西需要做三件事把加载和生成拆开、把 chat 模板这一层显式化、把批量与显存这两件事算清楚。这三件事做完脚本才具备被复用和调参的基础。3.1 最小可跑脚本tokenizer 与 chat template 的两处硬坑对话模型的输入不是纯文本是经过 chat template 拼装后的 token 序列。直接tokenizer(你好)喂进去模型看到的格式和训练时完全不一样输出会变得非常随意甚至自问自答。正确写法是把消息列表交给apply_chat_template并且一定要带add_generation_promptTrue。from transformers import AutoModelForCausalLM, AutoTokenizer import torch MODEL_PATH /home/user/work/glm4/weights # 解压后的目录不是 zip tok AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( MODEL_PATH, torch_dtypetorch.bfloat16, # 40 系/Ampere 及以上走 bf16老卡退 fp16 device_mapauto, # 单卡也建议写多卡时自动切分 trust_remote_codeTrue, ).eval() messages [{role: user, content: 用三句话说明 KV Cache 为什么能省算力}] inputs tok.apply_chat_template( messages, add_generation_promptTrue, # 少这一句模型不知道轮到自己说话了 return_tensorspt, return_dictTrue, ).to(model.device) with torch.no_grad(): out model.generate(**inputs, max_new_tokens256, do_sampleFalse) # 一定要切掉 prompt 部分否则会把你的问题一起打印出来 ans tok.decode(out[0][inputs[input_ids].shape[1]:], skip_special_tokensTrue) print(ans)几个参数各自的作用值得说清楚。torch_dtype决定权重加载精度bf16 在支持的卡上数值稳定性和速度都比 fp16 好老架构卡比如部分 V100 之前的卡不支持 bf16会直接报错或者退化成奇怪的数值。device_mapauto在单卡场景下等价于把整模型放在 0 号卡但写上之后换多卡不用改代码。add_generation_promptTrue是对话模板的关键开关它负责在序列末尾补上“助手开始回答”的标记。最后那个切片out[0][inputs[input_ids].shape[1]:]是硬要求generate返回的是 prompt 加新生成内容的完整序列。trust_remote_codeTrue这个参数要单独提一句。仓库里的模型实现代码会被动态加载执行路径正确时它读的是你解压出来的那份源码路径写错时它会去别的地方找于是出现“我明明改了仓库代码但效果没变”的玄学现象。排查这类问题在加载后打印一下model.__class__.__module__能立刻确认到底加载的是哪份实现。3.2 批量与流式把 generate 包一层单条推理验证完下一步就是把接口做成能接受列表输入。这里有个非常隐蔽的坑生成任务的 padding 方向必须是左边。因果语言模型是从左往右预测的右侧 padding 会让模型的“当前位置”错位批量结果互相污染表现为第一个问题答得还行后面几条答非所问或者直接复读第一条。# 生成任务必须左侧 padding这一行写在加载 tokenizer 之后 tok.padding_side left if tok.pad_token is None: tok.pad_token tok.eos_token texts [解释一下 LoRA 的低秩假设, 讲清楚量化里的 group size 是什么] prompts [ tok.apply_chat_template([{role: user, content: t}], add_generation_promptTrue, tokenizeFalse) for t in texts ] batch tok(prompts, return_tensorspt, paddingTrue).to(model.device) with torch.no_grad(): outs model.generate(**batch, max_new_tokens256, do_sampleFalse, pad_token_idtok.pad_token_id) for i, o in enumerate(outs): print(i, tok.decode(o[batch[input_ids].shape[1]:], skip_special_tokensTrue)[:120])pad_token缺失是另一个高频故障。很多对话模型的 tokenizer 默认没有 pad token批量编码时直接抛异常或者悄悄用了 eos 导致每行尾部多一个结束符。显式把pad_token设成eos_token是最省事的做法代价是极少数情况下模型会在无关位置停止生成可以接受。流式的做法是把generate丢到子线程主线程消费TextIteratorStreamer。这个模式的好处是不用改模型代码坏处是流式下没法做 beam searchnum_beams必须为 1。做内部工具时我一般默认开流式首字延迟从几秒降到几百毫秒体验差别很明显。from transformers import TextIteratorStreamer from threading import Thread streamer TextIteratorStreamer(tok, skip_promptTrue, skip_special_tokensTrue) kwargs dict(**inputs, max_new_tokens512, streamerstreamer, do_sampleFalse) thread Thread(targetmodel.generate, kwargskwargs) thread.start() for chunk in streamer: print(chunk, end, flushTrue) thread.join()3.3 精度与显存bf16、fp16、int4 的取舍显存不够是这类仓库落地时最实在的阻力。权重精度直接决定占用一个几十亿参数量的模型bf16 下光权重就要十几 GB加上 KV Cache 和中间激活单卡 24 GB 是很紧的。降精度的顺序我一般按“先降 KV Cache、再降权重、最后才上 4bit 量化”来走因为 4bit 对输出质量的损伤在小模型上比较明显。方案权重占用相对质量影响适用场景bf161.0无有 A100/4090 级别卡追求稳定fp161.0轻微长文本偶有溢出不支持 bf16 的老卡int8约 0.5小单卡 24 GB 跑长上下文int4约 0.25明显指令跟随变弱仅做演示或边缘设备实际调的时候先看max_new_tokens和上下文长度。KV Cache 的占用和batch_size × 序列长度 × 层数 × 隐藏维度成正比把并发从 8 降到 2往往比换量化版本更能解决问题而且不损失质量。这也解释了为什么很多人在压测时发现“单条跑得好好的一上并发就 OOM”——问题不在权重在缓存。4. 服务化与微调把 glm4 仓库改成一个能对外提供接口的工程脚本能跑通和能对外提供服务之间还差一个推理引擎和一层接口协议。自己用 FastAPI 包一层generate也能用但并发上来之后显存和吞吐会很难看原因是 HF 的原生generate没有做 continuous batching。这一步换引擎的收益远大于调参。4.1 用 vLLM 起一个 OpenAI 兼容服务换成 vLLM 之后仓库里的模型实现依然能被复用前提是权重目录结构是标准格式。启动命令里几个参数决定成败尤其是显存水位和最大长度这两个设大了直接起不来。python -m vllm.entrypoints.openai.api_server \ --model /home/user/work/glm4/weights \ --served-model-name glm4-local \ --dtype bfloat16 \ --max-model-len 8192 \ --gpu-memory-utilization 0.85 \ --trust-remote-code \ --port 8000--max-model-len是预分配的上下文长度上限设成 32768 会直接吃满显存启动阶段就报 KV Cache 不足按业务实际需要设宁可先小后大。--gpu-memory-utilization 0.85留 15% 给激活值和碎片设到 0.95 时看起来能塞下实际一有长请求就 OOM。--trust-remote-code在模型需要自定义实现时必须加否则加载阶段就失败。启动成功的标志是日志里打印出 “Uvicorn running” 并且模型加载耗时正常结束卡在这一步的话先把--max-model-len砍半再试。4.2 客户端调用与并发下的显存水位服务起来之后客户端用标准 OpenAI SDK 就能接这一点让它的迁移成本极低——原来调云端接口的代码只改base_url和model两个字段。from openai import OpenAI client OpenAI(base_urlhttp://127.0.0.1:8000/v1, api_keyEMPTY) resp client.chat.completions.create( modelglm4-local, messages[{role: user, content: 把这条日志归类ERROR timeout after 3 retries}], temperature0.2, # 分类任务压低随机性 max_tokens128, ) print(resp.choices[0].message.content)压测的时候要盯两个数显存占用和 P99 延迟。显存随并发上升是正常的但如果在某个并发值上突然台阶式跳变通常说明有请求触发了长度上限把 KV Cache 撑开了。这时候的处理不是加卡是把客户端侧的超长输入截断或者给服务端加一层长度校验。temperature这类采样参数在服务端不生效由客户端传所以线上一致性要靠调用方约束这一点很容易在联调时被忽略。4.3 LoRA 微调数据格式与显存账仓库里如果带了微调脚本它的入口一般是接受一个 jsonl。数据格式的细节决定训练能不能收敛最常见的问题是只写了问答对、没写角色标记训练出来的模型不会按对话格式输出。{messages: [{role: user, content: 把这句话改写得正式一些这玩意儿不太好使}, {role: assistant, content: 该功能目前存在可用性问题。}]}字段名要和仓库脚本里解析的那套对上有的是conversations、有的是messages写错不报错只是 loss 一直不变。训练启动参数里per_device_train_batch_size设 1、靠gradient_accumulation_steps凑等效批量是小显存下最稳的组合。python finetune.py \ --model_name_or_path /home/user/work/glm4/weights \ --train_file ./data/train.jsonl \ --max_seq_length 2048 \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 8 \ --learning_rate 1e-4 \ --num_train_epochs 2 \ --lora_rank 16 \ --lora_alpha 32 \ --output_dir ./out-lora \ --bf16lora_rank和lora_alpha的比值大致决定适配强度16/32 是一个比较温和的起点max_seq_length直接乘进显存2048 和 8192 的差距是数量级的。跑之前先抽 20 条数据确认模板拼出来的字符串长什么样比训练两小时后再去看 loss 曲线高效得多。5. 避坑与排查六个翻车现场的现象、原因和解法下面这几条都是我在不同机器、不同仓库分支上真实遇到过的按“现象 → 原因 → 解决”整理遇到问题可以按这个顺序对着排查。5.1 环境与依赖类装不上、装上了跑不动坑一import torch 报 undefined symbol。现象是pip install全部成功python -c import torch直接抛undefined symbol: c10::...。原因通常是机器上同时存在 conda 安装的 cudatoolkit 和 pip 装的 torch wheel动态库加载到了错误的那一份。解决办法是彻底清掉环境重建只用 pip 装 torch不要在同一个环境里混用 conda 的 CUDA 包。坑二flash-attn 编译半小时后失败。现象是安装过程长时间卡在 building wheel最后报编译器版本或内存不足。原因是这个包默认从源码编译对 nvcc 版本和内存都有要求。可行的做法是优先找与 torch、CUDA、Python 三者版本都匹配的预编译包实在找不到就在推理脚本里把 attention 实现切回默认功能不受影响只是长序列慢一些。坑三权重加载报 shape mismatch 或意外的 key。现象是加载到 90% 突然报某个张量维度不对。原因基本是权重版本和代码分支不匹配比如拿量化版的权重配了非量化的加载代码或者反过来。解决办法是对着权重目录里的config.json看量化相关字段确认加载代码走的是同一条分支不要凭目录名猜。5.2 推理与输出类能跑但答非所问坑四输出重复、复读、或者上来就自问自答。现象是不报错但回答质量明显不像这个量级的模型。原因多半是 chat template 没生效或者漏了add_generation_prompt。解决方式是先把模板拼出来的原始字符串打印出来看正常情况下应该能看到明确的角色标记和结尾的助手起始标记看不到就说明模板压根没应用上。坑五批量推理时第二条开始串味。现象是单条测都好一批量就互相污染。原因是padding_side用了默认的 right生成任务必须 left。修法是在加载 tokenizer 之后立刻设tok.padding_side left并显式设置pad_token。坑六nvidia-smi显示还有空余却 OOM。现象是显存占用显示 20/24 GB但请求一进来就报 out of memory。原因是显存碎片加上 KV Cache 预分配可用连续块远小于表面数字。处理方式是下调max_model_len、把gpu_memory_utilization从 0.95 降到 0.85或者直接减少并发。加卡之前先试这三条多数情况能省下一笔预算。注意六个坑里有四个的根因都是“路径或版本对不上”而不是代码有 bug。遇到报错先确认加载的到底是哪份代码、哪个权重目录比读源码快得多。6. 进阶验证用一段固定 prompt 集给仓库做回归改完 chat template、换过量化版本、调过生成参数之后最怕的是“感觉好像变差了但说不清哪里变差”。我的做法是准备一份固定的 prompt 集二十条左右覆盖四类任务指令跟随、信息抽取、多轮追问、长文本摘要。每条只保留输入和期望要点不写标准答案因为生成式输出没法逐字比对。import json, hashlib from difflib import SequenceMatcher from openai import OpenAI client OpenAI(base_urlhttp://127.0.0.1:8000/v1, api_keyEMPTY) cases [json.loads(l) for l in open(regress.jsonl, encodingutf-8)] def run(tag): rows [] for c in cases: r client.chat.completions.create( modelglm4-local, messages[{role: user, content: c[q]}], temperature0, max_tokens256, ) txt r.choices[0].message.content.strip() rows.append({id: c[id], tag: tag, out: txt}) return rows base run(base) new run(new) # 逐条算相似度低于 0.6 的人工看一眼 for b, n in zip(base, new): s SequenceMatcher(None, b[out], n[out]).ratio() if s 0.6: print(b[id], round(s, 2), |, n[out][:100])脚本里temperature0是为了让同一版本两次调用结果尽量稳定SequenceMatcher只是给一个粗粒度信号真正要看的是“改动前后哪几条回答变化最大”。如果变化集中在信息抽取任务上说明你的改动影响了指令跟随如果集中在长文本上多半是上下文截断。这套东西跑一遍不到十分钟但能挡掉大部分“上线后才发现效果回退”的事故。回归维度观察指标异常信号指令跟随是否按格式输出出现多余解释、格式跑偏信息抽取字段完整性漏字段、字段名改成中文多轮追问上下文引用是否正确忘记上一轮内容长文本是否被截断结尾突兀、重复开头我现在的习惯是任何一次动到 chat template、量化配置或者推理引擎版本的改动都先把这份 20 条的基线跑出来存一份改完再跑一份 diff两份输出摆在同一个窗口里看。这个习惯是被一次“只改了一个采样参数结果抽取准确率掉了十几个点”的事故逼出来的那之后我才明白模型工程的确定性不是靠感觉是靠可重复的对照。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑