简介面向ChatGLM系列大模型微调需求的实践资源包聚焦AI大模型应用与自然语言处理场景适合正在学习或落地大模型微调的开发者、算法工程师与科研人员。压缩包内共148个文件以58个Python脚本、36个Jupyter Notebook为核心附带13个TXT说明、12个PNG示意图以及YAML配置、Markdown文档、JSONL数据等辅助材料整体仅6.21MB便于快速下载查阅。内容覆盖LoRA、DreamBooth等参数高效微调方法既有可运行的训练脚本也有分步演示的Notebook同时包含配置文件与说明文档能够帮助读者从环境搭建、数据准备到模型微调与推理形成完整闭环。作者结合自身深耕AI大模型应用领域的实践经验整理而成针对账号开通、环境配置、落地应用等常见问题也提供了可参考的思路。目前已有235人学习适合需要快速上手并系统梳理微调流程的读者。1. ChatGLM大模型微调到底是什么从全参数训练的显存焦虑到LoRA的性价比当业务方把一份内部知识库文档递过来说“让ChatGLM大模型微调一下做个能用公司话术接待客户的助手”时最容易踩的第一脚是全参数训练——ChatGLM-6B虽然只有60亿参数全量微调的显存需求按百G算单卡根本装不下更别提多数团队手里只有一块消费级显卡。真正的解法是LoRA这类参数高效微调把原模型冻住只训练几十到几百万个新增参数。这篇文章不写浮在表面的概念从显卡选型、数据集构建、微调脚本到训练翻车的排错路径按实际项目顺序一步步拆给你。目标是让你在单张24G显卡上把ChatGLM大模型微调这件事跑通、跑稳并且知道哪些参数值得反复调、哪些参数纯属玄学。2. 微调前的硬件与环境先算清楚显存账再谈跑通ChatGLM-6B2.1 显卡与显存单张24G显卡就是舒适区8G卡也能凑合但别抱幻想先说结论如果你手头只有一块8G显存的卡ChatGLM-6B的LoRA微调也能跑但只能玩极限配置——batch_size1、seq_len压到512、模型用4bit量化加载训练速度慢且每一步都像在走钢丝。24G显存才是真正的舒适区RTX 3090、RTX 4090都能胜任你不用把整个晚上花在跟显存斗智斗勇上。我见过不少新手在这上面耽误好几天环境装好了一跑训练就OOM然后开始怀疑conda装错了、怀疑transformers版本不对一通排查最后发现就是显存不够。换张卡或者把序列长度降下来问题立刻消失。所以做这个方向之前先看一眼自己的硬件别拿8G卡硬扛6B模型那叫自虐。训练配置显存占用约实际体验4bit量化 LoRA batch1 seq5126~8G能跑紧巴巴适合尝鲜8bit量化 LoRA batch1 seq102412~14G舒适训练速度可观fp16 LoRA batch1 seq51215~18G消费级显卡上限fp16 全参数微调100G以上需要多卡A100不在本文范围为什么LoRA能省显存但省不了这么多这是很多人的认知误区LoRA只训练低秩矩阵反向传播时不更新原模型权重所以省掉了优化器状态那一大块显存。但前向传播和反向传播的激活值仍然要完整过一遍整个模型这部分显存跟全参数微调是一样的。换句话说显存大头是激活值不是LoRA那几个小矩阵。这个认知能帮你解释很多怪现象——比如LoRA参数量只有全模型的0.1%显存开销却比纯推理高四五倍。影响显存的三个可调参数要心里有数。第一个是batch_size每加1激活值近似翻倍第二个是seq_len注意力矩阵的显存和它是平方关系从512涨到1024注意力部分的显存涨4倍第三个是gradient_checkpointing用重计算换显存训练速度会慢20%~30%但显存能省一半。我一般会把它默认打开然后把batch拉到1再用gradient_accumulation_steps去模拟大batch这是最稳妥的省显存组合拳。2.2 基座模型下载与环境锁定从conda到transformers的依赖版本陷阱ChatGLM系列目前接触最多的基座是chatglm2-6b和chatglm3-6b。两者的加载方式、tokenizer用法基本一致微调代码可以复用区别在于chatglm3的对话模板更严格训练时prompt要按它的messages格式拼接这个细节到数据章节再展开。模型下载推荐用git lfs从HuggingFace克隆或者用ModelScope的Python API拉取模型文件大约12~13G下载完成后一定要检查文件完整性——缺一个分片会导致加载时莫名其妙报错。# 创建独立conda环境Python锁3.10避免新版本把老依赖搞崩 conda create -n chatglm-finetune python3.10 -y conda activate chatglm-finetune # 锁定核心依赖版本别追新追新容易翻车 pip install transformers4.41.2 peft0.11.1 bitsandbytes0.43.1 pip install accelerate0.30.1 datasets2.19.1 # 用git-lfs拉取模型权重断点续传比浏览器下载稳得多 git lfs install git clone https://huggingface.co/THUDM/chatglm3-6b这里每个依赖都不是随便装的。transformers负责模型加载和Trainer训练循环peft提供LoRA的实现版本不能太老否则不支持ChatGLM的target_modules写法bitsandbytes是做4bit和8bit量化的底层库它的版本跟CUDA版本强相关装完最好跑一段代码验证一下能不能正常调用accelerate是分布式和device_map的支撑库datasets用来加载jsonl格式的训练数据。一个经常被忽略的坑是transformers版本。如果你装了特别新的版本比如4.46以上ChatGLM的trust_remote_codeTrue加载方式偶尔会报兼容性错误因为ChatGLM的自定义代码还在用老接口。我一般会把版本锁定在4.41附近这个版本同时兼容ChatGLM2和ChatGLM3也兼容peft 0.11。版本锁定的意义在于你今天能跑通的代码三个月后重装环境还能跑通不会被上游更新悄悄弄坏。2.3 快速自检用一段加载代码确认显卡、驱动和依赖都正常环境装完先别急着准备数据花两分钟做一个冒烟测试确认模型能加载、显卡能计算。这一步能帮你把“环境问题”和“代码问题”隔离开后面训练翻车时少一个怀疑对象。import torch # 第一步确认显卡可用顺手看显存总量和已占用 print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0)) print(torch.cuda.mem_get_info()) # 返回(空闲显存, 总显存) # 第二步加载模型和tokenizerChatGLM需要trust_remote_codeTrue from transformers import AutoTokenizer, AutoModelForCausalLM model_id ./chatglm3-6b tokenizer AutoTokenizer.from_pretrained(model_id, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_id, trust_remote_codeTrue, torch_dtypetorch.float16, device_mapauto ) # 第三步生成一句测试文本确认推理链路通 inputs tokenizer(你好, return_tensorspt).to(cuda) out model.generate(**inputs, max_new_tokens20) print(tokenizer.decode(out[0]))这段代码里的trust_remote_codeTrue是ChatGLM系列绕不开的开关因为它的模型定义不在transformers仓库里而是放在模型目录下的代码文件中。这个开关安全敏感建议只在你确认过来源的模型目录上开启。torch_dtypetorch.float16是为了让模型以半精度加载显存占用比fp32少一半。如果冒烟测试能正常输出一段中文文本说明环境链路没问题可以放心进入数据准备阶段。3. 把业务数据做成ChatGLM能“听懂”的指令对数据格式、清洗与质量体检3.1 指令微调数据格式Alpaca三字段还是ChatGLM的messages格式ChatGLM大模型微调用得最多的是指令微调路线就是把业务问题写成“问题答案”的结构化数据让模型学会在给定指令下输出期望的回答。最通用的格式是Alpaca式三字段结构每个样本一行JSON{instruction: 根据以下材料回答用户问题只使用材料中的信息。, input: 材料公司售后政策规定7天内无理由退货拆封产品除外。问题拆封的产品能退吗, output: 不能。根据售后政策拆封产品不享受7天无理由退货。}其中instruction是任务指令input是要处理的内容output是期望的模型输出。input可以为空比如“以下哪项不是公司主营产品A.xxx B.xxx”这种纯问题可以直接把内容写进instruction。还有一个细节是ChatGLM3带了一套更严格的对话模板训练时如果按messages格式构造数据需要在模板里区分用户轮和助手轮。我的实际经验是用Alpaca三字段格式做微调代码最简单效果也很稳如果你要用官方chat模板做多轮对话微调才需要走messages格式但数据构造复杂度和踩坑概率都会上升。3.2 从业务文档到指令对清洗、拆分与人工抽检的土办法拿到一份内部知识库文档最直接的做法是让标注人员手工写指令对质量最高但速度慢。实际项目里我一般先用脚本把长文档自动切成块再让业务方在切好的块上做修改和标注这样效率能提升不少。切分逻辑要按语义边界不能硬切——硬切出来的半句话会让模型学到错误的拼接方式。import json import re def split_doc(text, max_len300): 按句号切分再按max_len合并避免把一句话切成两半 sentences re.split(r(?[。]), text.strip()) chunks, cur [], for s in sentences: if len(cur) len(s) max_len and cur: chunks.append(cur) cur s else: cur s if cur: chunks.append(cur) return chunks # 把切好的块包装成Alpaca格式样本 raw_text 公司成立于2010年主营业务是……。售后政策规定……。 samples [] for i, chunk in enumerate(split_doc(raw_text)): samples.append({ instruction: 根据以下材料回答用户的问题只使用材料中的信息。不要编造。, input: 材料 chunk, output: , # 待标注人员填写 }) # 输出成jsonl每行一个样本方便后续用datasets加载 with open(train_raw.jsonl, w, encodingutf-8) as f: for s in samples: f.write(json.dumps(s, ensure_asciiFalse) \n)这个脚本的价值在于把“阅读-切分-包装”这个机械工作自动化了人工只需要填写output。数据量方面几百条高质量数据就能看到明显的格式风格变化要覆盖更多业务场景则需要两三千条。数据量不是越多越好几千条乱标的数据不如几百条精心复核的数据这是微调数据和我最开始想象的完全相反的地方。注意output为空是为了人工标注方便训练前一定要把所有空output补齐或过滤掉否则模型的生成结果会学到“沉默”这种坏习惯。3.3 数据质量体检三个必做的脚本化校验提前把训练埋的雷排掉数据准备完不能直接开训先跑一段校验脚本查三类常见问题空输出、重复样本、超长样本。其中超长样本是最隐蔽的坑因为ChatGLM的context窗口虽然有几K但训练时过长的序列会直接推高显存甚至让batch_size1都OOM。如果不做检查你会在训练启动后十分钟被一个长度5000的样本炸掉然后在排障上浪费两小时。import json from collections import Counter data [json.loads(line) for line in open(train.jsonl, encodingutf-8)] # 校验1空输出这种样本训练时等于教模型生成空白 bad [i 1 for i, d in enumerate(data) if not d.get(output, ).strip()] print(f空样本行号: {bad}) # 校验2完全重复的样本重复太多会导致模型对同一条数据过拟合 texts [d[instruction] d[input] d[output] for d in data] dup_count len(texts) - len(set(texts)) print(f重复样本数: {dup_count}) # 校验3长度分布超过模型窗口的样本要么截断要么删掉 lens [len(d[instruction] d[input] d[output]) for d in data] print(f最大长度: {max(lens)}, 平均长度: {sum(lens) / len(lens):.0f}) print(超过1024的样本数:, sum(1 for l in lens if l 1024)) # 手工把超长样本截断或拆分不要指望模型自己处理超长输入 data [d for d in data if len(d[instruction] d[input] d[output]) 1024]这个脚本我用在每一个微调项目里跑一遍只需要几秒钟但能避免训练跑到一半被脏数据打断。还有一个容易被忽略的点是特殊字符比如不可见字符、全角半角混用、HTML标签残留。这些字符模型能接受但会让模型学到奇怪的输出格式。如果数据是从网页或PDF里抽出来的建议先用正则把HTML标签、多余空白符清掉再做校验。数据质量这一关做得越细后面训练阶段的坑就越少这算是微调实战里最划算的时间投入。4. 用LoRA在单卡上跑通ChatGLM微调PEFT配置、Trainer参数与权重合并4.1 用PEFT给ChatGLM装LoRA“外挂”target_modules和秩的选择逻辑LoRA的原理可以一句话讲清在模型每个attention层的权重旁边加两个低秩矩阵A和B训练时只更新这两个小矩阵AB的乘积近似权重增量。所以lora微调在工程上的表现就是“冻结原模型新增小参数”这也是为什么它经常和adapter微调混着叫。对ChatGLM-6B来说LoRA的可训练参数量通常只有全模型的0.1%到0.5%这直接决定了用它做微调不需要多卡A100单张消费级显卡就能跑。from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig import torch model_id ./chatglm3-6b # 用4bit量化加载基座训练时只更新LoRA参数基座权重保持冻结 quant_config BitsAndBytesConfig( load_in_4bitTrue, # 4bit量化显存占用直接砍半 bnb_4bit_quant_typenf4, # 推荐用nf4比fp4精度高 bnb_4bit_compute_dtypetorch.bfloat16, # 计算时用bf16兼顾精度和速度 bnb_4bit_use_double_quantTrue, # 双重量化可再省一点显存 ) tokenizer AutoTokenizer.from_pretrained(model_id, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_id, quantization_configquant_config, trust_remote_codeTrue, ) # 配置LoRA这是lora微调最核心的参数段 from peft import LoraConfig, get_peft_model lora_config LoraConfig( r8, # 低秩矩阵的秩可学习参数量的直接决定者 lora_alpha16, # 缩放系数最终权重增量 alpha/r * AB target_modules[query_key_value], # ChatGLM把Q、K、V合并在一个层里这里是它的名字 lora_dropout0.1, # 防止小参数过拟合的dropout biasnone, # 不训练bias进一步省显存 task_typeCAUSAL_LM, # 因果语言模型任务 ) model get_peft_model(model, lora_config) model.print_trainable_parameters() # 打印可训练参数量方便确认是否只训练了LoRA这里有两个容易翻车的参数需要细说。第一个是target_modules必须写成query_key_value因为ChatGLM的注意力实现是QKV合并成一个全连接层层名就叫这个。如果你照抄LLaMA项目的q_proj, v_projLoRA会静默地不挂到任何层上训练跑完loss在降但模型其实没被改到。第二个是r和lora_alpha的比例r8配合alpha16意味着实际缩放倍数是2这是社区里验证过的稳妥搭配。r越大拟合能力越强但数据量小时过拟合风险也越大我一般先从r8起步效果不满意再升到16。4.2 用Trainer跑通微调学习率、梯度累积与checkpoint策略数据集用上一章准备的jsonl加载即可用datasets库一行就能读进来。关键参数都在TrainingArguments里这些参数值得逐项理解它们决定了训练是稳定收敛还是野生震荡。from datasets import load_dataset from transformers import TrainingArguments, Trainer # 直接加载jsonl不需要额外写Dataset类 dataset load_dataset(json, data_filestrain.jsonl, splittrain) train_args TrainingArguments( output_dir./chatglm3-lora-out, # checkpoint保存目录 per_device_train_batch_size1, # 单卡batch设为1显存压力最小 gradient_accumulation_steps8, # 梯度累积8步等效batch8 learning_rate2e-4, # LoRA微调常用起点1e-4到5e-4之间 num_train_epochs3, # 数据量小时3轮足够多了容易灾难性遗忘 logging_steps10, # 每10步打印一次loss方便盯曲线 save_strategyepoch, # 每个epoch存一次checkpoint save_total_limit2, # 最多保留2个checkpoint防止磁盘爆掉 fp16True, # 半精度训练显存省一半速度也更快 lr_scheduler_typecosine, # 余弦退火收敛更平滑 warmup_ratio0.03, # 前3%的步数线性预热防止开头loss飞掉 report_tonone, # 不接wandb等外部平台避免上报失败卡训练 ) trainer Trainer( modelmodel, argstrain_args, train_datasetdataset, tokenizertokenizer, ) trainer.train()learning_rate是微调结果最敏感的参数没有之一。lora微调的常见做法是从2e-4开始跑如果loss震荡剧烈就降到1e-4如果收敛太慢可以试试3e-4但超过5e-4基本都会让loss失控。gradient_accumulation_steps8配合batch1等效于batch_size8但显存占用只有八分之一。注意有效batch的增大会让收敛更稳但也会让训练时间变长如果你的数据只有几百条梯度累积设4就够了。还有一个实际建议fp16在部分显卡上有loss溢出的风险如果训练中loss突然变NaN第一件事就是把fp16关掉或者改用bf16。这里训练脚本写成fp16是因为多数消费级显卡在fp16下提速明显但跑的时候要盯着前几百步的loss曲线——如果出现NaN不要慌关掉fp16重试这是最简单的排查动作。4.3 合并LoRA权重把Adapter和基座变成一个能独立部署的模型训练完成后目录下会有一堆checkpoint里面存的是LoRA adapter权重不是一个完整模型。直接拿着adapter去部署是不行的需要决定两件事一是用哪个checkpoint——我一般选验证集效果最好的那个epoch而不是最后一个二是要不要把adapter合进基座。合并的好处是推理时加载更快、部署更简单坏处是如果以后想换一个adapter必须重新加载基座。实际项目里我会两种都用实验阶段保留adapter灵活切换上线前合并成完整模型。from peft import PeftModel from transformers import AutoModelForCausalLM import torch model_id ./chatglm3-6b # 用fp16重新加载基座不需要再走量化 base_model AutoModelForCausalLM.from_pretrained( model_id, torch_dtypetorch.float16, trust_remote_codeTrue, ) # 加载训练好的adapter权重注意路径指向checkpoint目录 adapter PeftModel.from_pretrained(base_model, ./chatglm3-lora-out/checkpoint-300) # 合并并保存为独立模型之后推理不再需要peft库 merged adapter.merge_and_unload() merged.save_pretrained(./chatglm3-merged)合并后的模型目录结构和原基座一致包含模型权重和配置文件可以像加载原版ChatGLM一样直接加载不需要peft代码参与。这里有一个容易忽略的点合并时要保证基座的加载精度和训练时一致训练时是4bit量化合并时不能也拿4bit量化模型去merge量化误差会被写进合并权重里。正确做法是先加载fp16的基座再挂adapter最后merge这样合并结果最干净。5. ChatGLM微调避坑手册OOM、loss为NaN、灾难性遗忘与重复生成5.1 现象24G显存也在第一步OOM训练刚启动就翻车训练脚本一跑日志还没打印几步就报CUDA out of memory这是最常见的翻车现场。很多人第一反应是显卡不行其实排查顺序应该是先看seq_len是不是被某条超长样本拉爆了再检查batch_size是不是写成了2最后看gradient_checkpointing有没有开。我遇到过最离谱的一次是数据里混进一条一万字的合同文本batch1也OOM因为序列长度的显存开销是二次方增长。解决办法是按顺序试先开gradient_checkpointing这会省掉大约30%到50%的显存然后把per_device_train_batch_size降到1最后给tokenizer加truncationTrue和max_length1024把超长样本截断。这三个方法组合下来基本没有跑不动的ChatGLM微调。注意max_length这个参数很多人忘了加它不止是保护显存也能防止训练数据里的极端长度让模型对padding位置产生错误学习。5.2 现象loss变成NaN训练中断在某个epoch训练跑到某个steploss突然从2.1跳成NaNtrainer报错退出。这种现象在LoRA微调里最隐蔽的两个原因是fp16数值溢出和数据里混入了异常字符。先看数据如果某一条样本里有特殊控制字符或乱码feed进模型后embedding计算会出NaN再看精度fp16在部分显卡上对极小或极大的梯度值容易溢出尤其chatglm3-6b这类6B模型某些层对精度极度敏感。解决路径是先把异常字符过滤掉用正则或者直接肉眼检查train.jsonl里有没有奇怪的符号如果loss还是NaN把fp16关掉或换成bf16。bf16在Ampere架构及以上的显卡上不但不会溢出速度还比fp16更稳。这里有一个判断技巧如果NaN发生在warmup阶段之前多半是数据问题如果发生在训练中段多半是精度问题。5.3 现象微调后通用能力崩塌原来会的数学推理都变差了这是lora微调最容易让人沮丧的现象业务问答变好了但模型其他能力明显退化甚至“你好”都不太会回应了。这个现象叫灾难性遗忘根因通常是三个叠在一起epoch太多、rank太大、训练数据太单一。比如数据只有500条客服问答你让r16跑5轮模型把所有能力都用来拟合这500条的分布自然就把原来的通用能力覆盖了。解决办法是epoch从3降到2r从16降到8同时最好是混合一批通用对话数据进去。我一般会在训练集里混入5%到10%的通用问答对这些数据不用多只需要保住模型的语言基础能力。混数据要控制比例通用数据太多会让微调效果不明显太少又起不到保护作用5%是实测比较舒服的比例。记住一个原则lora微调更适合改变模型的语言风格和输出格式而不是灌入大量新知识——新知识靠数据对风格靠微调改。5.4 现象训练时loss在降生成时输出全是重复话模型训练一切正常loss曲线漂亮地往下走但推理时输出变成了“好的好的好的好的”或者一段话反复说。这个现象和微调关系不大多半是解码参数的问题。训练时模型学会了在你的数据分布上生成但你用naive的贪心解码或者过高的temperature去生成模型就在低置信区反复绕圈。解决方法是调整生成参数而不是重新训练temperature设为0.7到0.8之间太高容易绕圈太低会变得死板top_p设为0.9no_repeat_ngram_size2这能直接禁止模型重复出现相同的2-gram。还有一个很多人不知道的点ChatGLM的repetition penalty参数设1.1到1.2之间对重复话有奇效。这些参数组合好后同一套checkpoint的输出质量会有一个肉眼可见的提升而代价只是改几行推理代码值得先试再考虑重训。5.5 现象训练后回答格式对但内容驴唇不对马嘴模型确实学会了你的输出格式但内容经常和问题无关或者从材料中引用了不存在的信息。这类问题有两种常见原因。第一种是训练模板拼接错误比如训练时字段是“材料xxx 问题xxx”推理时却只传了“问题xxx”模型没见过这种输入格式自然胡乱生成。解决方法是把训练和推理的prompt拼接封装成同一个函数两边都调用它杜绝格式漂移。第二种原因是数据本身的input和output语义脱节。我踩过一次自动切分脚本把材料和答案切到不同的chunk里模型的“参考答案”其实是另一段材料的内容。这种数据不会让loss升高它会安静地教模型学会“答非所问”。所以数据清洗时对自动生成的样本一定要人工抽检至少5%到10%的样本别指望脚本能发现语义错位——脚本只能查格式问题查不了内容语义。6. 微调成果怎么验证才靠谱对比基线、生成参数调优与量化部署实战6.1 用同一组测试集对比微调前后别靠感觉判断效果微调完成先不要急着部署准备10条真实业务问题一条条喂给原版ChatGLM和微调后的模型把两组输出打印出来对比。对比时我一般只看三类差异格式是否按业务规范、内容是否引用了训练材料里的信息、通用能力是否明显下降。这三类分别对应微调的三个目标——风格迁移、知识注入、能力保留。import torch from transformers import AutoTokenizer, AutoModelForCausalLM model_id ./chatglm3-merged tokenizer AutoTokenizer.from_pretrained(model_id, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_id, torch_dtypetorch.float16, trust_remote_codeTrue, device_mapauto ) def generate(prompt, temperature0.7, top_p0.9): # 推理参数固定下来才方便做前后对比 inputs tokenizer(prompt, return_tensorspt).to(cuda) out model.generate( **inputs, max_new_tokens256, temperaturetemperature, top_ptop_p, no_repeat_ngram_size2, repetition_penalty1.1, ) return tokenizer.decode(out[0][inputs[input_ids].shape[1]:], skip_special_tokensTrue) test_prompt 根据以下材料回答公司对拆封产品的退货政策是什么材料拆封产品不享受七天无理由退货。 print(generate(test_prompt))这段代码里最值得关注的是生成参数全部固定下来。很多人在验证时随手调参数结果根本没意识到输出差异是参数造成的还是微调造成的。固定的参数组合也让后续迭代有可比性。我一般会微调前先跑一遍记录结果微调后跑同一遍差异一眼可见。不要只看一条输出至少看10条——这个项目值不值得投入在这10条对比里比任何训练日志都有说服力。6.2 量化部署到业务服务器把微调后的ChatGLM压进16G内存验证通过的模型可以准备上线了。如果业务服务器显存紧张可以用bitsandbytes做4bit量化推理把微调合并后的模型压到6~8G显存这块已经相当成熟。量化推理相比fp16会有一定的效果损失但业务场景里通常感知不明显换来的是成本大幅下降这也是很多企业做私有化部署时的常见选择。from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig import torch model_id ./chatglm3-merged quant_config BitsAndBytesConfig( load_in_4bitTrue, bnb_4bit_quant_typenf4, bnb_4bit_compute_dtypetorch.bfloat16, ) tokenizer AutoTokenizer.from_pretrained(model_id, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_id, quantization_configquant_config, trust_remote_codeTrue, device_mapauto )量化部署的显存占用需要实测不要只看资料里的数字。同一个模型在batch_size1和max_new_tokens128下占用大约6~8G但如果你把max_new_tokens拉到1024KV cache会显著提高显存。上线前用压测脚本模拟最高并发确认显存和延迟都在承受范围内再切流量。我见过不少团队在部署时踩同一个坑本地测试单条很流畅上线后发现并发一高就OOM原因是并发时KV Cache线性叠加而他们没有预留余量。说句实在话我做ChatGLM微调的第一个项目就翻了车——数据没做长度校验训练到一半被一条超长样本炸掉接着又因为贪心解码看到一堆重复输出一度怀疑是训练失败。后来才明白训练和推理本来就是两套问题先跑通流程再调效果才是这个方向最靠谱的推进方式。希望这篇笔记能帮你少走一遍这些弯路把时间花在真正影响结果的数据和参数上而不是跟环境斗智斗勇。本文还有配套的精品资源点击获取