
简介面向AI研发工程师与算法学习者这份以LoRA微调实战为核心的学习资料包聚焦LlamaAlpaca LoRA与ChatGLMChatGLM Tuning两大主流模型的低秩适配训练。包内围绕用户故事生成、测试代码生成、代码辅助生成、文本转SQL、文本生成代码等真实研发提效场景提供了完整实验流程与实现参考覆盖从数据准备、模型微调到结果验证的各个环节帮助读者理解LoRA原理并快速上手自定义微调任务包括关键超参设置、数据格式构造与训练效果评估。资源包共93个文件总大小约53.46MB包含20个jsonl训练数据文件、18个ipynb交互式笔记本、11个md说明文档、8个py自动化脚本以及pdf、txt、csv等辅助材料。其中ipynb文件对应alpaca-lora与chatglm-tuning的逐步实现过程py脚本涵盖数据合并、文本转代码等工具jsonl提供用户故事、测试代码、SQL生成等场景的样本数据md与pdf则记录了实验笔记、原理讲解与目录说明整体结构清晰适合按模块学习或直接复用也便于二次开发。目前已有442人浏览学习这份资源包适合对大模型微调有一定基础、希望借助完整案例动手实践LoRA训练并提升AI研发效能的读者无论是学习研究还是工程落地都能从中获得直接可参考的实战经验。1. LoRA 自训为什么是 AI 研发提效的最短路径很多团队在引入大模型时卡在同一个地方跑通推理很容易但想让模型贴合自己的业务数据微调这一步要么没卡跑不动要么外包出去既贵又怕数据泄露。自己动手训练 LoRA本质上是用参数高效微调把这件事的成本降下来——不碰全部权重只训练一小批低秩矩阵让 LoRA 适配器学会业务指令Llama 和 ChatGLM 这类开源基座都能用这套思路改造。实际做下来一张消费级显卡就能微调 7B 量级的模型从准备数据到拿到可用权重一个工作日足够。适合已经会用开源模型做推理、想在内部数据上做定制化的研发团队也适合作为团队里的一个通用提效工具沉淀下来。2. 动手前先定三件事基座模型、数据格式与训练框架LoRA 训练的门槛不在代码而在前置选择。基座模型决定了你要处理什么 tokenizer、什么对话格式数据格式决定了模型能不能学会指令跟随训练框架决定了显存占用和出问题的排查难度。这三件事不先定清楚训练脚本写出来也很难直接跑通。2.1 基座模型怎么选Llama 系与 ChatGLM 系的差异我一般会根据业务语言倾向先定基座。Llama 系中文场景下一般用它的中文增量预训练版本对英文和代码类指令表现稳定tokenizer 只认文本结构对话格式完全靠 prompt 模板控制灵活性高ChatGLM 系自带中文对话的分词器和相对固定的对话组织方式中文指令跟随的默认表现更好但灵活性略低。两者在训练侧有几个实际差别。Llama 系的 tokenizer 没有 pad token加载后必须手工指定否则批训练时数据打包会报错或损失计算异常ChatGLM 系自带 pad token少一个坑。Llama 系是标准因果语言模型用TaskType.CAUSAL_LMChatGLM 系虽然结构上是前缀语言模型但在主流 LoRA 训练实践中同样可以通过 peft 的CAUSAL_LM任务类型跑通这不影响 LoRA 适配器的正确性。对照项Llama 系ChatGLM 系中英文倾向英文/代码较强中文需选中文基座中文默认表现好pad token无需手动设置自带对话格式prompt 模板控制自由度高结构化角色对话固定性好常见任务类型CAUSAL_LMCAUSAL_LM实践中通用选基座时我有个习惯先拿模型跑一次你业务里最典型的 20 条提示词看原始输出有多离谱。如果基座本身连基本指令都听不懂LoRA 是救不回来的LoRA 做的是调整风格、格式和领域知识补不了基座的基础能力。2.2 数据格式Alpaca 格式与 ChatGLM 格式怎么组织数据格式是整个训练里最值得花时间的部分。LoRA 微调的效果上限由数据质量决定这一点被很多人忽略。当前最常见的做法是用 Alpaca 式指令数据格式每条样本包含指令、可选输入和期望输出保存成 JSON 或 JSONL。Llama 系和 ChatGLM 系在 peft transformers 训练路径下都能用这套格式但组织要略作调整。Llama 系单轮指令用instruction input output三字段就够ChatGLM 系如果要保留多轮关联需要把历史对话合并进输入字段。我的建议是第一版数据只做单轮指令不要上来就挑战多轮。多轮对话的构造方式不同训练脚本差异很大容易排查不清。先拿 50 条高质量单轮样本跑通链路再扩展多轮。数据文件参考结构data/train.json # 训练集 data/valid.json # 验证集建议至少 20 条训练集每条样本的 JSON 结构{ instruction: 把下面这句话翻译成英文, input: 今天天气很好。, output: The weather is nice today. }单轮样本构造时我一般会注意三点一是instruction要写清楚任务边界不依赖示例猜测二是input可空但空字段建议保留而不是删除三是全量样本覆盖业务场景要均匀别让某类指令占 80%模型学出来会偏。2.3 训练框架与显存预算训练框架我推荐 peft transformers accelerate 的组合。peft 负责注入 LoRA 适配器transformers 负责加载模型与训练循环accelerate 负责多卡和混合精度。三者都是当前主流方案遇到问题检索起来容易找到参考。显存预算直接决定你能不能在本机跑。7B 量级的模型加载半精度权重大约占 14GB 显存纯 LoRA 训练的优化器状态和激活值额外占用不多16GB 显存的消费级显卡基本可以跑13B 量级半精度加载就要 26GB单卡 24GB 会很紧张通常要配合 8bit 量化加载。训练脚本里可以加上load_in_8bitTrue来压显存但要留意它会影响训练速度显卡能装下时我更倾向半精度直接跑。3. 自己动手训练Llama 与 ChatGLM 两套 LoRA 脚本下面两套脚本是我在实际项目中常用的训练路径一套面向 Llama 系一套面向 ChatGLM 系。两者的核心差异在 tokenizer 处理和模型加载参数上LoRA 配置基本一致。脚本在单卡、16GB 显存环境下验证可用数据量 500 条、训练 3 个 epoch 大约 3060 分钟。3.1 Llama 系从加载到训练的完整脚本import torch from transformers import ( AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer, DataCollatorForSeq2Seq ) from peft import LoraConfig, get_peft_model, TaskType from datasets import load_dataset model_path /data/models/llama-7b # 换成你的基座模型路径 data_path ./data/train.json # 加载 tokenizer并手动补上 pad token tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) tokenizer.pad_token tokenizer.eos_token # 关键Llama 系无 pad token # 加载模型半精度 单卡 model AutoModelForCausalLM.from_pretrained( model_path, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) # 注入 LoRA 适配器 lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, r8, lora_alpha16, lora_dropout0.05, target_modules[q_proj, v_proj, k_proj, o_proj], ) model get_peft_model(model, lora_config) model.print_trainable_parameters() # 确认可训练参数占比 # 构造指令数据 def format_sample(sample): text f[INST] {sample[instruction]} {sample[input]} [/INST] {sample[output]} return {text: text} def tokenize_fn(sample): result tokenizer(sample[text], truncationTrue, max_length512) result[labels] result[input_ids].copy() return result dataset load_dataset(json, data_filesdata_path)[train] dataset dataset.map(format_sample).map(tokenize_fn, remove_columnsdataset.column_names) # 训练参数 training_args TrainingArguments( output_dir./lora-llama, per_device_train_batch_size2, gradient_accumulation_steps8, learning_rate2e-4, num_train_epochs3, logging_steps10, save_strategyepoch, fp16True, remove_unused_columnsFalse, ) trainer Trainer( modelmodel, argstraining_args, train_datasetdataset, data_collatorDataCollatorForSeq2Seq(tokenizer), ) trainer.train()tokenizer.pad_token tokenizer.eos_token这行是所有 Llama 系 LoRA 训练里最容易被漏掉的设置。少了它批处理时 pad 位置可能填成未知 token训练进程本身可能不报错但损失和生成质量都会变得奇怪。target_modules指定注入 LoRA 的线性层q/k/v/o 四个投影层是常见做法覆盖注意力计算的主要矩阵r8是低秩矩阵的秩lora_alpha16是缩放系数两者配合决定适配器容量。fp16True开启混合精度显存占用和训练速度都能受益。3.2 ChatGLM 系脚本差异与三个注意点import torch from transformers import ( AutoModel, AutoTokenizer, TrainingArguments, Trainer, DataCollatorForSeq2Seq ) from peft import LoraConfig, get_peft_model, TaskType from datasets import load_dataset model_path /data/models/chatglm-6b # 换成你的基座模型路径 data_path ./data/train.json tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) # ChatGLM 加载时建议加 dtype 参数避免默认 float32 显存爆炸 model AutoModel.from_pretrained( model_path, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, r8, lora_alpha16, lora_dropout0.05, target_modules[q, v], ) model get_peft_model(model, lora_config) def format_sample(sample): prompt sample[instruction] if sample.get(input): prompt sample[input] return {prompt: prompt, response: sample[output]} def tokenize_fn(sample): # 用固定分隔符连接 prompt 和 responseresponse 部分参与损失计算 prompt_text sample[prompt] response_text sample[response] full_text prompt_text [SEP] response_text encoded tokenizer(full_text, truncationTrue, max_length512) labels encoded[input_ids].copy() # 只对 response 部分计算损失避免模型只学会复读 prompt prompt_encoded tokenizer(prompt_text [SEP]) prompt_len len(prompt_encoded[input_ids]) labels[:prompt_len] -100 encoded[labels] labels return encoded dataset load_dataset(json, data_filesdata_path)[train] dataset dataset.map(format_sample).map(tokenize_fn, remove_columnsdataset.column_names) training_args TrainingArguments( output_dir./lora-chatglm, per_device_train_batch_size2, gradient_accumulation_steps8, learning_rate2e-4, num_train_epochs3, logging_steps10, save_strategyepoch, fp16True, remove_unused_columnsFalse, ) trainer Trainer( modelmodel, argstraining_args, train_datasetdataset, data_collatorDataCollatorForSeq2Seq(tokenizer), ) trainer.train()第一个注意点ChatGLM 系target_modules用[q, v]还是[query, value]取决于模型权重里的实际命名。加载模型后先打印model.state_dict().keys()确认不要照抄网上配置权重命名不同会导致适配器没有注入到任何模块训练完模型根本没变化。第二个注意点trust_remote_codeTrue必须保留这类模型的实现依赖仓库里的自定义代码去掉它加载阶段就会失败。第三个注意点构造labels时把 prompt 部分置为-100这个做法比直接让模型对全序列计算损失更合理能让模型专注学习怎么回答而不是背下提问方式。3.3 启动命令与训练日志观察脚本保存成train_lora.py后启动命令保持简单python train_lora.py显存不够时先看是不是其他进程占卡了用nvidia-smi确认。日志里主要盯着loss的变化趋势正常情况第一个 epoch 内 loss 会明显下降如果三个 step 过去 loss 纹丝不动先停掉检查学习率和数据格式不要盲目加长时间。4. LoRA 关键参数rank、alpha、学习率与数据长度怎么调LoRA 训练参数少但每个都直接决定最终效果。我见过不少项目把默认参数一跑了事效果不对就怀疑数据其实多半是参数不合适。这一章把几个必调参数讲透。4.1 rank 与 alpha适配器容量与训练稳定性的平衡r是低秩矩阵的秩可以理解为 LoRA 适配器的可学习容量。r8处理常见指令跟随和风格迁移足够领域数据量大且差异明显时提到16r64这种大秩在部分中文指令微调里出现过效果更好的案例但随之而来的是过拟合风险和训练时间上涨。lora_alpha是缩放系数常规设置为alpha 2 * r即r8时alpha16。这个比例给出的适配权重变化幅度在多数任务里表现稳定alpha调大相当于加大 LoRA 对输出的影响但过大可能破坏基座原有能力。参数常见范围调参方向r8 / 16 / 32数据量大、任务难时调大lora_alpha16 / 32通常取 2 倍 r看输出变化幅度微调lora_dropout0.0 / 0.05 / 0.1数据量小的时候调大防过拟合target_modulesq/v 或 q/k/v/o按模型权重实际命名确认lora_dropout容易被忽略。数据量只有几百条时0.05的 dropout 能明显降低过拟合数据量几千条以上时调回0.0也可以性能影响不大。判断依据很简单训练 loss 降得很低但验证集表现差先加 dropout。4.2 学习率、batch 与 epochLoRA 与全参微调的参数差异LoRA 的学习率设置跟全参微调有显著差异。全参微调常用5e-5到1e-4而 LoRA 只训练少量新增参数学习率可以放开一个量级2e-4到3e-4是常见区间。学习率过低时适配器几乎学不到东西表现是 loss 缓慢下降但生成文本跟基座没区别学习率超过5e-4则容易震荡loss 曲线上下跳动后期生成质量不稳。batch 的策略受显存约束。per_device_train_batch_size在 16GB 显存、7B 模型下通常只能设到 2 或 4靠gradient_accumulation_steps凑等效 batch size。我一般把两者乘积控制在 16 到 32 之间等效 batch 过大在小数据量上反而容易欠拟合。epoch 方面几百条数据 3 个 epoch 通常够用1 个 epoch 效果往往不充分超过 5 个 epoch 过拟合风险急剧上升。验证方法是看训练结束后模型在验证集上的输出是否出现复读、空答。4.3 max_length 与 padding损失被稀释的隐形开关max_length直接决定训练样本截断长度也影响显存占用。设置太短长指令的关键信息被截掉设置太长短样本被迫填充大量 pad token模型会在这些位置计算无意义损失。常见做法是统计训练集中样本长度分布取 90 分位长度作为max_length这样既保住长样本又不让短样本填充太多。损失稀释问题在训练日志里看不到但最终效果会暴露。模型输出变得短而敷衍时除了检查数据还要确认 padding 部分的标签是否被正确忽略。上面脚本里我用DataCollatorForSeq2Seq动态 padding它不会把 pad 位置纳入损失计算但如果自己写了数据整理函数很容易把 pad token 的标签也留下导致模型花大量精力学习预测无意义的 pad token。排查方式是把训练样本里labels中-100之外的 token 数量统计出来对比总长度如果有效标签占比过低就要调整 max_length 或数据截断策略。5. LoRA 训练避坑五条亲测踩坑记录LoRA 训练表面简单实际踩坑点集中且隐蔽。下面五条都是我在不同项目里真实遇到过的按「现象 → 原因 → 解决」的方式整理新手照着排查能省大半天时间。5.1 loss 在下降生成结果却像在复读输入现象训练日志里 loss 正常下降但推理时模型把用户输入原样重复一遍或者输出跟输入高度相似。原因数据构造时没有把输入部分排除在损失计算之外。模型把「阅读输入并输出」学成了「完整复述输入」因为在它看来预测下一个 token 的最稳妥策略就是复读。很多脚本只构造了input_ids而忘记构造labels或者labels直接拷贝了全部input_ids导致前面提到的 prompt 部分也参与损失计算。解决手动构造labels把 prompt 部分置为-100只让 response 部分计算损失。5.2 显存明明够训练却报 OOM现象模型加载成功但训练循环一开始就报 CUDA out of memory。原因remove_unused_columnsFalse只解决了数据列问题但激活值显存峰值发生在反向传播阶段。batch size 设 2 能加载设 4 可能就爆。另外device_mapauto在多卡机器上会把模型切到多张卡单卡显存看起来空闲但训练时数据传输和激活值计算可能集中在某一侧。解决先把per_device_train_batch_size降到 1验证能跑后再逐步加回检查是不是多卡机器上device_mapauto把模型分散了单卡训练时去掉这个参数实在压不下来再加上load_in_8bitTrue或load_in_4bitTrue用量化换显存。5.3 训练完的 adapter 加载到另一台机器上效果完全不对现象同一份 adapter 文件在训练机器上推理效果不错拷到部署机器上重新加载基座模型后输出变得不可用。原因最常见的是精度不一致。训练时基座模型用float16或 8bit 加载部署机器用float32加载LoRA 适配器的数值是在原精度下学出来的换精度后权重激活范围变化适配信号失真。其次是基座模型权重版本不一致基座权重差一个版本adapter 全部失效。解决部署时严格保持与训练一致的torch_dtype记录训练所用的基座版本部署前校验模型目录哈希有条件的话部署时直接加载合并后的完整权重绕开适配器与基座版本耦合问题。5.4 数据量几千条反复训练但仍学不到业务规则现象训练集覆盖了某类业务规则loss 也降了但推理时模型对这类问题答非所问。原因数据格式里instruction和input混在一起或者instruction本身写得不规范同一类任务表述千差万别模型没有学到统一模式。更隐蔽的原因是数据里正负样本不均衡某类指令下只有正例没有反例模型学不到边界。解决先做数据清洗把同类指令的表述统一成固定模板检查样本分布每条 instruction 对应的输出确保覆盖不同正确的表达再做一次「链路验证」用 20 条数据训练跑通确认这个数据能学出可用结果再扩大数据量。数据质量问题是 LoRA 训练里最花时间的部分没有捷径。5.5 训练日志显示 loss 数值很低但模型实际输出乱码现象loss 从高位降到接近 0推理时模型输出却是乱码或重复标点。原因labels构造错误导致模型学习到了错误的注意力模式常见是 tokenizer 返回的attention_mask与input_ids长度不一致或在拼接[SEP]等分隔符时没有同步处理 labels。更常见的是 pad token 在 labels 里没有被忽略模型学会了预测 pad token 本身。解决检查 tokenizer 编码后的input_ids、attention_mask、labels三者长度用脚本打印一条样本确认-100的位置分布打印trainer中实际输入 batch 的形状确认 padding 后各字段同步。乱码问题九成出在数据整理函数里不要先怀疑模型和框架。6. 训练完还不够LoRA 合并、权重校验与部署验证模型训练完成只算走了一半。最后一个阶段要回答三个问题这个 LoRA 到底学到了什么、部署时用哪种方式加载、有没有可复现的验证流程。我的做法分三步走。第一步是做「过拟合链路自检」。正式训练前拿 20 条样本训 1 个 epoch目标不是效果而是确认链路通。如果 20 条数据训练后模型能在这些样本上复现出接近原文的输出说明数据构造、损失计算、适配器注入全链路正常。这一步能过滤掉大部分脚本问题避免拿几百条数据训了半天才发现基础 bug。第二步是选择部署加载方式。LoRA 训练产物一般是 adapter 权重目录包含adapter_config.json和adapter_model.bin。部署时有两种常见做法# 方式一基座 adapter 组合加载推荐保留可迭代性 from peft import PeftModel from transformers import AutoModelForCausalLM, AutoTokenizer base_model AutoModelForCausalLM.from_pretrained( base_model_path, torch_dtypetorch.float16, device_mapauto, ) model PeftModel.from_pretrained(base_model, ./lora-llama/checkpoint-3) # 方式二合并权重生成一个完整的推理模型 merged_model model.merge_and_unload() merged_model.save_pretrained(./merged-model) tokenizer.save_pretrained(./merged-model)方式一保留了 adapter 单独存储的特性业务迭代时只需要重新训练 adapter不用动基座权重方式二更适合部署到生产环境推理框架加载完整模型少一层依赖也不存在 adapter 与基座版本错配的问题。我一般两个都保留迭代阶段用方式一上线用方式二。第三步是效果验证。不要只看 loss要拿 1020 条训练集外的问题逐条看输出关注三个点格式是否遵循要求、领域术语是否准确、模型是否还在回答与任务无关的内容。如果大部分样本通过再进入团队试用。我自己在这条路上踩过最大的坑就是赶进度跳过链路自检结果花两天时间在错误脚本上反复调参。现在养成的习惯是任何数据变更后都先跑 20 条过拟合实验再放量训练看起来多花了十几分钟实际省下的是以小时计的返工时间。这套方法适配器和脚本一旦沉淀下来换数据、换基座都能复用希望帮到你。本文还有配套的精品资源点击获取