
简介这份资源面向希望借助 LoRA 微调提升研发效率的算法工程师与 AI 应用开发者围绕 LlamaAlpaca LoRA与 ChatGLMChatGLM Tuning两条技术路线展开覆盖用户故事生成、测试代码生成、代码辅助生成、文本转 SQL、文本生成代码等典型研发场景适合具备一定深度学习基础、想动手实践参数高效微调的中高级读者。压缩包共 93 个文件约 53.46MB以 jsonl 数据集、ipynb 训练笔记、md 说明文档、pdf 参考资料和 py 脚本为主另含少量 json、csv、txt 及图片素材兼顾数据、代码与文档三类用途。目前已有 442 人学习下载。资源按 alpaca、chatglm、datasets、codegen、text-to-code、text-to-sql、userstory 等模块组织配套 merge-jsonl.py、text-to-code.py、text-to-sql.py 等脚本与训练日志便于读者复现微调流程、理解数据构造方式并迁移到自身业务场景中验证效果。1. 从一张 24G 显卡说起LoRA 微调到底在省什么很多人第一次动 LoRA 的念头都是被显存逼出来的。手里只有一张 24G 的卡想拿 Llama 或 ChatGLM 做领域适配全参数微调连权重带优化器状态直接爆掉于是开始搜「lora微调是什么意思」「lora训练」这类词。LoRA 的思路很朴素冻结原模型权重只在注意力层的部分矩阵旁边挂一对低秩矩阵训练时只更新这对小矩阵。它省的不是计算量而是可训练参数量和优化器显存——通常能把可训练参数压到原模型的千分之一到百分之一量级。这篇笔记讲的是自己动手训练 LoRA覆盖两条主流路线Llama 系以 Alpaca LoRA 那套流程为代表和 ChatGLM 系。适合手上有单卡或双卡、想跑通第一个领域 LoRA 的工程师也适合已经跑过但 loss 不降、显存炸、合并后效果崩的人。下面从环境、数据、训练参数一路讲到合并与验证中间会重点说清楚哪些参数是真有用的哪些是玄学。2. 训练环境与基座选型Llama 和 ChatGLM 该先动哪个2.1 两条路线的差异与选型理由Llama 系和 ChatGLM 系在 LoRA 训练上的差别主要不在 LoRA 本身而在 tokenizer、注意力实现和对话模板。Llama 用 SentencePiece中文场景下 token 效率偏低同样一段中文token 数往往比 ChatGLM 多出三到五成直接后果是显存占用和训练时长都上去了。ChatGLM 原生中文 tokenizer中文语料下更省且它的对话格式[Round 1]\n\n问...\n\n答...在社区工具里支持得比较成熟。选型上我的建议是语料以中文为主、显卡在 24G 以内先动 ChatGLM要做中英混合或后续想接生态里大量英文指令数据选 Llama。Alpaca LoRA 那套脚本本质是给 Llama 系做的指令微调流程它的价值在于把数据格式、训练循环、合并脚本串成了一条可复现的线理解它之后再迁移到 ChatGLM 会快很多。环境上PyTorch 版本要和 CUDA 对齐别在这上面省事。常见做法是用 conda 建独立环境装torch、transformers、peft、datasets、accelerate这几个核心包。peft是 LoRA 的实现库accelerate负责多卡和混合精度调度。版本之间耦合比较紧transformers和peft尽量用同期发布的版本否则容易出现target_modules名字对不上的问题。2.2 环境搭建与依赖安装的可复现步骤先建环境再装包顺序别乱。下面这套命令在单卡 24G 机器上验证过CUDA 12 系列。# 建独立环境Python 版本建议 3.10 conda create -n lora_train python3.10 -y conda activate lora_train # 装 PyTorch按自己 CUDA 版本选对应 index-url pip install torch2.1.0 torchvision --index-url https://download.pytorch.org/whl/cu121 # 装训练相关依赖版本尽量对齐 pip install transformers4.36.0 peft0.7.0 datasets2.16.0 accelerate0.25.0 pip install sentencepiece protobuf scipy装完先验证 GPU 能不能被识别别急着下模型。import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0)) # 期望输出True 和你的显卡型号torch.cuda.is_available()返回 False 的话八成是 CUDA 版本和 PyTorch 编译版本不匹配重装对应 wheel 即可别去改系统驱动。transformers和peft的版本对应关系要留意peft 0.7配transformers 4.36是稳的跨大版本容易在LoraConfig的target_modules解析上报错。2.3 基座模型下载与目录组织模型权重建议单独放一个目录训练脚本、数据、输出分开后面排查问题会省很多事。mkdir -p ~/lora_work/{models,data,output,scripts} # 以 ChatGLM3-6B 为例权重放到 models 下 # 目录结构大致是 # ~/lora_work/models/chatglm3-6b/ # config.json tokenizer.model pytorch_model-*.binLlama 系同理把基座权重放models/llama-7b/。权重文件格式上现在社区越来越多用safetensors加载更快也更安全transformers直接支持不用额外处理。目录组织清楚之后训练脚本里所有路径都用绝对路径或基于工作目录的相对路径避免在不同目录下跑脚本时找不到文件。3. 数据准备指令数据的格式与清洗3.1 指令数据的标准结构LoRA 微调的效果七成看数据。指令微调的数据一般是「指令 输入 输出」三段式Alpaca 格式就是典型[ { instruction: 把下面这句话翻译成英文, input: 今天天气不错, output: The weather is nice today. }, { instruction: 给这段代码写注释, input: def add(a, b): return a b, output: 该函数接收两个参数并返回它们的和。 } ]instruction是任务描述input是可选上下文output是期望回答。没有input的任务把input留空字符串即可别删字段否则模板拼接时容易错位。ChatGLM 的对话格式略有不同通常把多轮对话组织成prompt和response两列训练时再套它的对话模板。数据量上领域适配起步 500 到 2000 条高质量样本就能看到明显变化别一上来堆几万条低质数据。质量比数量重要得多一条自相矛盾的样本能把模型带偏。3.2 数据清洗与去重的实操清洗主要做三件事去重、去空、去超长。下面这段脚本处理 Alpaca 格式的 json。import json from hashlib import md5 def clean_alpaca(path_in, path_out, max_len2048): seen set() kept [] with open(path_in, r, encodingutf-8) as f: data json.load(f) for item in data: ins item.get(instruction, ).strip() out item.get(output, ).strip() inp item.get(input, ).strip() # 去空指令或输出为空直接丢 if not ins or not out: continue # 去超长按字符粗筛真正长度以 tokenizer 为准 if len(ins) len(inp) len(out) max_len: continue # 去重用指令输入做指纹 key md5((ins inp).encode(utf-8)).hexdigest() if key in seen: continue seen.add(key) kept.append({instruction: ins, input: inp, output: out}) with open(path_out, w, encodingutf-8) as f: json.dump(kept, f, ensure_asciiFalse, indent2) print(f原始 {len(data)} 条清洗后 {len(kept)} 条) clean_alpaca(data/raw.json, data/clean.json)max_len这里按字符粗筛真正判断要等 tokenizer 编码后看 token 数因为中英文 token 比例差很多。去重指纹用「指令 输入」不把输出算进去是因为同一指令配不同输出往往意味着数据本身有冲突这种应该人工看而不是靠去重脚本一刀切。3.3 数据集划分与 tokenizer 编码清洗完要划分训练集和验证集常见比例 9:1 或 95:5。验证集不是摆设它是你判断有没有过拟合的唯一依据。from datasets import Dataset from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained( models/chatglm3-6b, trust_remote_codeTrue ) def format_sample(item): # 按基座对应的对话模板拼接这里以通用指令模板示意 text f### 指令:\n{item[instruction]}\n if item[input]: text f### 输入:\n{item[input]}\n text f### 回答:\n{item[output]} return {text: text} raw Dataset.from_json(data/clean.json) raw raw.map(format_sample) split raw.train_test_split(test_size0.1, seed42) print(split)trust_remote_codeTrue在加载 ChatGLM 这类带自定义代码的模型时必须加否则会报找不到模型类。模板拼接要和推理时用的模板完全一致训练用一套、推理用另一套是新手最常见的翻车点之一模型会答非所问。4. LoRA 训练参数配置与启动4.1 LoraConfig 的关键参数怎么设LoRA 的核心参数就几个r、lora_alpha、lora_dropout、target_modules。r是低秩矩阵的秩越大表达能力越强但参数越多常见 8 到 64领域适配起步用 8 或 16 就够。lora_alpha是缩放系数经验上设成r的两倍比较稳比如r8配alpha16。lora_dropout防过拟合小数据集上设 0.05 到 0.1。target_modules是最容易出错的地方。Llama 系一般是q_proj、v_proj有的实现会加上k_proj、o_proj。ChatGLM 的注意力层命名不同常见是query_key_value。名字写错不会报错但 LoRA 会挂到错误的层上训练 loss 照降效果却很差这是典型的黑匣子式翻车。from peft import LoraConfig, get_peft_model, TaskType lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, r8, # 低秩维度领域适配起步值 lora_alpha16, # 缩放系数通常为 r 的 2 倍 lora_dropout0.05, # 小数据集防过拟合 target_modules[query_key_value], # ChatGLM 用这个Llama 换成 q_proj/v_proj biasnone, # 不训练 bias省显存 ) model get_peft_model(base_model, lora_config) model.print_trainable_parameters() # 期望看到可训练参数占比在 0.1% ~ 1% 之间print_trainable_parameters()这一步别省它是你确认 LoRA 真的挂上去的唯一快速手段。如果可训练参数占比接近 100%说明target_modules没匹配上LoRA 没生效。4.2 训练超参与 Trainer 配置超参上学习率是重灾区。LoRA 的学习率通常比全参微调大常见 1e-4 到 3e-4全参微调那套 2e-5 用在 LoRA 上会慢到怀疑人生。batch size 受显存限制单卡 24G 跑 6B 模型per_device_batch_size 设 1 到 4再用梯度累积把等效 batch 拉到 16 到 64。from transformers import TrainingArguments, Trainer training_args TrainingArguments( output_diroutput/chatglm3-lora, per_device_train_batch_size2, gradient_accumulation_steps8, # 等效 batch 2 * 8 16 learning_rate2e-4, # LoRA 常用区间 num_train_epochs3, logging_steps10, save_strategyepoch, evaluation_strategyepoch, fp16True, # 24G 卡开 fp16 省显存 warmup_ratio0.03, lr_scheduler_typecosine, report_tonone, ) trainer Trainer( modelmodel, argstraining_args, train_datasetsplit[train], eval_datasetsplit[test], ) trainer.train()gradient_accumulation_steps和per_device_train_batch_size的乘积才是真正影响训练稳定性的等效 batch。等效 batch 太小loss 会剧烈震荡太大收敛慢且可能过拟合。fp16True在部分模型上会出现 loss 变 nan遇到就换bf16True前提是显卡支持。warmup_ratio给 0.03 左右让学习率从低往高爬避免一开始就把预训练权重带偏。4.3 启动训练与日志观察启动后重点盯三样loss 曲线、显存占用、验证集指标。训练 loss 平稳下降是基本要求如果前几十步就掉到接近 0多半是数据泄漏或标签错位。验证集 loss 先降后升就是过拟合信号该减 epoch 或加 dropout。# 单卡直接跑 python scripts/train_lora.py # 多卡用 accelerate 启动 accelerate launch --num_processes2 scripts/train_lora.py显存占用上6B 模型 LoRA 训练在 24G 卡上通常占 14G 到 20G留点余量给激活值。如果 OOM优先降per_device_train_batch_size其次开梯度检查点gradient_checkpointingTrue它会用时间换显存训练慢一些但能跑起来。5. 避坑与排查训练 LoRA 最容易翻车的五件事5.1 loss 不降或直接变 nan现象训练几十步后 loss 卡住不动或者突然变成 nan。原因通常是学习率过高、fp16 数值溢出、数据里有空样本。解决先把学习率降到 1e-4 试再把fp16换成bf16最后检查数据清洗有没有漏掉空 output。三者按顺序排查别同时改否则不知道是哪个起的作用。5.2 合并后效果比训练时差很多现象训练时验证集表现正常合并权重后推理答非所问。原因多半是训练和推理的对话模板不一致或者合并时基座版本和训练时不是同一个。解决把训练脚本里的模板函数和推理脚本里的模板函数抽成同一个文件共用合并前核对基座权重的 hash。这个坑血泪经验最多模板差一个换行都能让效果崩。5.3 target_modules 写错导致 LoRA 没生效现象训练能跑loss 也降但效果和没微调差不多。原因target_modules名字和模型实际层名不匹配LoRA 挂到了不存在的层上peft有时不报错。解决加载模型后打印所有线性层的名字确认目标层名。for name, module in base_model.named_modules(): if isinstance(module, torch.nn.Linear): print(name) # 从输出里挑出注意力层的真实名字填进 target_modules5.4 显存够但训练极慢现象显存没满但每步耗时远超预期。原因数据加载没开多进程、序列长度设得过长、或者没开混合精度。解决dataloader_num_workers设 2 到 4max_length按数据实际分布设别一律 2048大部分样本可能只有几百 tokenpadding 到 2048 纯属浪费。5.5 验证集 loss 上升但训练 loss 还在降现象训练 loss 一路降验证 loss 中途反弹。原因过拟合数据量小或 epoch 太多。解决减 epoch、加lora_dropout、加数据。别指望靠调学习率救过拟合根子在数据量和训练轮数上。小数据集上 2 到 3 个 epoch 通常就够再多就是让模型背答案。6. 合并权重与效果验证把 LoRA 真正用起来训练完的 LoRA 权重是独立的小文件推理时可以直接加载也可以合并进基座。直接加载适合快速验证合并适合部署。合并脚本如下from peft import PeftModel from transformers import AutoModelForCausalLM, AutoTokenizer base AutoModelForCausalLM.from_pretrained( models/chatglm3-6b, trust_remote_codeTrue, device_mapauto ) model PeftModel.from_pretrained(base, output/chatglm3-lora) # 合并并保存 merged model.merge_and_unload() merged.save_pretrained(output/chatglm3-merged, safe_serializationTrue) tokenizer AutoTokenizer.from_pretrained( models/chatglm3-6b, trust_remote_codeTrue ) tokenizer.save_pretrained(output/chatglm3-merged)merge_and_unload()把 LoRA 权重加回基座并卸载适配器safe_serializationTrue存成safetensors格式。合并后一定要做对比验证别合并完就直接上线。验证方法上我一般准备一组固定测试集分别用「基座」「基座 LoRA」「合并后模型」跑同一批问题人工对比。重点看三类领域内问题是否答得更准、通用问题是否退化、格式是否稳定。领域 LoRA 常见的副作用是通用能力下降如果退化明显说明训练数据太单一或学习率过高。进阶一点的做法是控制变量做消融固定数据只改r8 / 16 / 32各训一版看验证集指标和显存占用的权衡。多数领域任务上r16是性价比拐点再往上收益递减而显存和过拟合风险都涨。另一个技巧是分层学习率注意力层给正常学习率其他层给更小的但这个在 LoRA 上收益有限因为可训练参数本来就少不建议新手一上来就折腾。我自己踩过最深的坑是第一次训完直接合并上线结果线上效果和验证集对不上回头查了两天才发现推理脚本的模板比训练时少了一个换行。从那以后我养成一个习惯训练脚本和推理脚本共用同一个build_prompt函数改一处两边都变。这个习惯比任何调参技巧都值钱。希望帮到你。本文还有配套的精品资源点击获取