多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

LoRA大模型微调实战:低秩适配原理与工程避坑指南

LoRA大模型微调实战:低秩适配原理与工程避坑指南 LoRA 这个名字最近在大语言模型圈子里几乎成了“省钱的代名词”。微调一个 7B 甚至 13B 级别的模型过去意味着几百 GB 显存、一块让人肉痛的数据中心显卡现在一张 24GB 的消费级卡就能跑而且效果并不差。靠的就是 LoRA 这种参数高效微调技术再加上 Hugging Face 把整套流程打包成了几步就能调好的工具箱。这篇文章写给两类人一类是想给模型做垂直领域能力定制的人另一类是刚买了显卡跃跃欲试的本地模型玩家。我会把原理、参数、代码和训练全程中真正会踩到的坑一次性讲透让你少走几周的弯路。1. 先弄明白 LoRA 到底在做什么以及它为什么能省这么多资源1.1 全量微调的昂贵代价很多人第一次意识到微调大模型的门槛不是在算法层面而是在显存账单面前。拿一个 7B 参数模型来说用全量微调Full Fine-tuning的思路去训光权重用 bf16 格式存下来就要吃掉大约 14GB 显存。但训练不止要存权重还要存梯度、Adam 优化器状态一阶动量 m 和二阶动量 v通常各占 4 字节再加上激活值、中间变量一张 24GB 显卡根本上不了场。传统全量微调意味着你要把整套参数全部回传、全部更新一次这背后的算力和显存开销就是一道实实在在的物理门槛。我之前跟朋友开玩笑说全量微调一个 7B 模型的显存需求约等于把家用车改装成卡车去运货运力是有了但绝大多数人根本用不到这么大的运力。我们日常做垂直领域适配比如让模型学会客服话术、法律条文问答、电商评论分析模型本身的基础能力已经足够强了缺的只是在特定分布上的“定向修正”。为了这一点修正去反传全部 7B 参数属于杀鸡用牛刀。1.2 低秩分解的核心思路LoRA 的做法很聪明全称是 Low-Rank Adaptation低秩适配。它不更新原始权重矩阵 W而是在旁边挂两条小矩阵 A 和 B让输入经过 W 的路径保持不变同时新增一条“旁路” BAx。前向计算变成h Wx BAx其中 A 通常是高斯随机初始化B 初始化为零这样训练开始时 BA 为零模型行为不发生突变。训练时你把原模型的全部参数冻结住只更新 A 和 B。A 的维度是 r × dB 的维度是 d × rr 就是秩实践中 r 取 8、16、32 就已经覆盖了绝大多数场景。也就是说参数更新量从原来那个 d×d 的大矩阵压缩成了两个小矩阵的乘积可训练参数比例往往只有 0.1% 左右。这里的逻辑值得展开一下微调的本质是什么是在预训练权重的基础上让模型往某个领域方向上移动一小段距离。LoRA 背后的假设是这段“移动量”是低秩的可以用少量参数表达。好比一个大图书馆几百万本书你要针对“厨房菜谱”这个方向做整理不需要把图书馆所有书架全挪一遍只需要在几个关键书架上加装标签索引就能达到效果。实验也证明在多数任务上 r16 左右的秩就足够逼近全量微调的效果。1.3 为什么 Hugging Face 生态让 LoRA 变得这么普及LoRA 最原始的论文出来时还需要手工魔改模型代码把低秩旁路插入到各个线性层里听起来就没那么友好了。Hugging Face 的 PEFTParameter-Efficient Fine-Tuning库把这个过程封装成了“配置文件 一行调用”的形式你只需要告诉它“我要改哪些模块、秩是多少、缩放系数是多少”transformers 的模型就会自动被包装成带 LoRA 旁路的结构。少了这一步LoRA 的普及不可能这么快。同样重要的是PEFT 训练出来的 LoRA 权重被保存成独立的 adapter 文件通常只有几十 MB 到一百多 MB。这意味着什么它和底座模型完全解耦。我有一份训练好的客服 LoRA可以挂在 Llama、Qwen、Mistral 任意同结构的底座上做切换不用重复存几个几十 GB 的模型副本。这在实际项目里非常爽也让模型资产的交付方式从几十 GB 的巨型文件变成了一个小压缩包。2. 选好你的武器装备Hugging Face 生态下的 LoRA 工具链和硬件预算2.1 环境搭建与依赖选型开始动手前先把轮子准备好。一个标准的 LoRA 训练环境通常包含PyTorch 2.x、transformers、datasets、peft、accelerate、trl可选、bitsandbytes如果走 QLoRA 量化路线。我常用的安装命令大致如下pip install torch transformers datasets peft accelerate bitsandbytes pip install trl # 如果要用 SFTTrainer 走更简洁的流程这里特别想聊一下 trl。它的 SFTTrainer 把数据加载、tokenize、训练封装得更顺滑对新手来说是好事可以直接传原始文本列表内部帮你完成拼接和切分。但我的建议是如果你是第一次做微调最好先用原生的 Trainer 手写一遍数据处理逻辑搞清楚 token 是怎么被拼起来的这样后面出问题时你知道去哪里排查。工具越封装调试越黑盒。transformers 和 peft 的版本需要注意。transformers 4.40 及以上版本对 Llama、Qwen 等主流模型的 LoRA 支持更完善一些细节改动比如 attention 的接口更新会影响 target_modules 的命名。我踩过一次旧版本 transformers 加载新模型权重报错的坑后来统一用 4.44 以上版本才安稳。2.2 模型怎么选base 版还是 chat 版这是新手最容易纠结的问题。拿一个中文场景举例你手里有 Qwen2.5-7B它同时提供了 Base 版和 Instruct 版。Base 版是纯预训练模型没有经过指令对齐输出风格非常“续写”但你拿来微调时反而更自由可以完全自己定义对话范式适合做大量自有专有数据的场景。Instruct 版已经学会了通用对话格式微调时只需在它现有风格上做小幅调整少数据量下的表现通常更稳。我个人的经验是如果你的训练数据不超过 1 万条且很多是通用类指令选 Instruct 版当底座更省事如果你要训的是一个特殊格式任务比如让模型输出结构化 JSON、从病历文本里抽字段用 Base 版自己定义输出范式的上限更高。数据量大了以后底座本身的风格影响会被冲淡反而是数据质量决定最终效果。也别盲目追求大参数量7B 级别的模型在 LoRA 加持下垂直任务往往不输 30B 全量微调的工程化效果而且显存要求低一个量级。2.3 一张图理清显存预算LoRA 虽然只训练极少参数但显存消耗并不只取决于可训练参数量。显存大头其实在激活值上面就是前向传播时各层中间输出的累积。一个粗略的估算公式我说一下模型权重占 2d 字节bf16梯度占 2d 字节优化器状态按 Adam 算要 8d 字节但这里的 d 是“可训练参数”而不是全量参数所以 LoRA 在这部分非常省剩下的变量就是激活值它和 batch size、序列长度、层数直接相关。我做过的几个实测配置给大家做个参考模型规模精度可参考配置训练方式2B 级bf16RTX 4070 12GBbatch4LoRA7B 级bf16RTX 4090 24GBbatch2grad accum4LoRA7B 级4bit NF4RTX 3090 24GBbatch4QLoRA13B 级4bit NF4RTX 4090 24GBbatch1grad accum8QLoRA这里最关键的一个变量是序列长度也就是 max_length。很多人训练指令模型习惯把所有样本塞到 4096 甚至 8192显存立刻爆炸。如果只是普通指令问答2048 附近是一个甜点值它已经覆盖绝大多数单轮问答和常见多轮对话。长上下文任务可以单独用序列长度分桶策略不要一上来就挑战模型极限。3. 完整实操从数据准备到 LoRA 训练跑通3.1 数据格式怎么组织单轮指令与多轮对话训练数据格式直接决定模型学成什么样这一步值得花时间打磨。先看最基本的 Alpaca 风格三个字段instruction指令、input可选的输入、output期望输出。{ instruction: 用一句话介绍广州的春天, input: , output: 广州的春天潮湿温暖木棉花开满街头属于岭南最热闹的季节。 }这种格式适合任务型数据。但如果你想训练多轮对话能力用 messages 格式更合适它天然支持多轮上下文{ messages: [ {role: user, content: 帮我推荐一部电影。}, {role: assistant, content: 你更喜欢科幻还是爱情题材}, {role: user, content: 科幻吧最近想看硬核一点的。}, {role: assistant, content: 那我推荐《星际穿越》它既讲人伦情感也涉及相对论和黑洞的硬核设定。} ] }在代码里如果用 Qwen 这类带 chat template 的模型最省心的方式是直接用 tokenizer.apply_chat_template它会把 messages 里的角色轮流拼接成模型熟悉的格式。注意一定要保证训练时的对话模板和推理时一致否则模型学了一百遍推理时用一个陌生的分隔符效果直接打折。很多宣称多轮对话能力差的自训练模型排查到最后就是模板不一致。还要强调一个经验数据里不要只有一问一答要刻意制造上下文依赖。比如上面的例子第二轮的“科幻吧”单独看没有意义模型只有读过第一轮才知道用户在延续话题。如果你训练数据全是独立问答模型会在多轮场景里“失忆”无法正确引用历史信息。3.2 数据清洗与配比这一步是容易被忽视但上限最高的环节。先说清洗太长、太短、重复、含乱码的样本都要过滤。我的经验口径是4 到 2048 个 token 之内的样本利用率最高太短学不到结构太长训练效率低且显存压力大。重复样本更危险它会造成局部过拟合模型对这组答案背得滚瓜烂熟换一种问法就失灵。再谈配比。如果你手上有不同来源的数据比如真实用户对话、人工撰写的语料、从报告里抽的结构化文本不要简单粗暴直接混在一起。试想一个问题电商评论分析任务里1 万条“好评分析”和 100 条“差评分析”放在一起训模型大概率会偷懒把多数样本预测成好评模式。对这种类别不平衡我通常会按业务重要性给数据集设定采样权重或者对少数类做轻微重复采样让模型至少在每个方向上都见过足够的样本。3.3 关键参数配置详细解读LoRA 训练常用的核心参数就那几个但每一个都值得弄明白。先列一个清单lora_alpha低秩矩阵的缩放系数。前向计算时 BAx 的结果乘以 alpha/r控制 LoRA 分支对原始输出的“侵入强度”。alpha 太大会导致训练初期行为突变太小又学不动。rrank低秩投影的秩。r8 适合简单任务r16 是绝大多数任务的首选r32 适合数据量大、任务复杂的场景。rank 再往上加收益边际递减而且显存和训练时间跟着涨。target_modules决定 LoRA 插到模型的哪些模块里。对 Llama 和 Qwen 这类主流模型常见配置是 q_proj、k_proj、v_proj、o_proj也就是自注意力里的 Q、K、V、O 四个投影矩阵。有的人会再加 gate_proj、up_proj、down_proj也就是 MLP 层效果有一点提升但参数量也多一些。learning_rateLoRA 训练的学习率往往比全量微调大一个量级。全量微调通常用 1e-5 到 5e-5LoRA 用 1e-4 到 3e-4 更常见。原因很好理解只有 0.1% 的参数在更新尺度越小步子反而要迈得大一点才能产生有效变化。但太大了会震荡我推荐新手先从 2e-4 起步。我做客服模型时常用的一组配置是 r16、lora_alpha32、target_modules 四项全选、learning_rate2e-4、dropout0.05、batch size2、gradient_accumulation_steps8。这个配置在 7B 模型和 2048 序列长度下能稳定收敛而且效果通常不差。3.4 核心训练代码从加载到 Trainer现在把上面的理论落成代码。下面是一份能直接运行的完整训练脚本骨架基于 transformers 和 peftimport torch from transformers import ( AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer, DataCollatorForLanguageModeling, ) from peft import LoraConfig, get_peft_model, TaskType from datasets import load_dataset model_id Qwen/Qwen2.5-7B tokenizer AutoTokenizer.from_pretrained(model_id, trust_remote_codeTrue) if tokenizer.pad_token is None: tokenizer.pad_token tokenizer.eos_token model AutoModelForCausalLM.from_pretrained( model_id, torch_dtypetorch.bfloat16, device_mapauto, trust_remote_codeTrue, ) lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, r16, lora_alpha32, lora_dropout0.05, target_modules[q_proj, k_proj, v_proj, o_proj], biasnone, ) model get_peft_model(model, lora_config) model.print_trainable_parameters() # 数据处理假设 datasets 里每行是 instruction / input / output dataset load_dataset(json, data_filestrain.jsonl)[train] def process_func(examples): texts [] for inst, inp, out in zip(examples[instruction], examples[input], examples[output]): if inp: user_part f用户{inst}\n{inp} else: user_part f用户{inst} assistant_part f助手{out} messages [ {role: user, content: user_part}, {role: assistant, content: assistant_part}, ] # 使用模型自带的 chat template text tokenizer.apply_chat_template(messages, tokenizeFalse, add_generation_promptFalse) texts.append(text) return tokenizer(texts, truncationTrue, max_length2048, paddingFalse) tokenized_ds dataset.map(process_func, batchedTrue, remove_columnsdataset.column_names) training_args TrainingArguments( output_dir./lora_qwen_7b, per_device_train_batch_size2, gradient_accumulation_steps8, learning_rate2e-4, num_train_epochs2, lr_scheduler_typecosine, warmup_ratio0.05, logging_steps10, save_steps500, bf16True, gradient_checkpointingTrue, ) trainer Trainer( modelmodel, argstraining_args, train_datasettokenized_ds, data_collatorDataCollatorForLanguageModeling(tokenizertokenizer, mlmFalse), ) trainer.train()这里的 DataCollatorForLanguageModeling 会把样本拼成一个 batch并且把标签设为输入 ID 的复制模型训练时不计算 padding 位置的损失。需要注意一点apply_chat_template 之后文本末尾往往已经带了 eos_token不用再额外追加。trainer.train() 跑起来之后你在终端里会看到类似这样的日志{loss: 1.2345, learning_rate: 0.0002, epoch: 0.21}loss 的绝对值无法跨模型横向比较重点看趋势。前几百步它可能在 1.0 到 2.0 之间浮动随着训练推进会慢慢下降。如果你的 loss 从一开始就在零点几甚至趋近于零先不要高兴很可能是数据出了问题比如每一条回答都一样模型只是在死记硬背。3.5 增量训练实战LoRA 做领域预测除了监督微调LoRA 还可以做增量预训练继续训练的活。比如你用一批行业论文、专利文本让模型增强某个专业领域的“语感”这类任务不需要标准的问答对只需要纯文本做续训。把每篇文档做成长段文本加 eos_token用 Causal LM 目标直接训练即可。和 SFT 相比学习率要更小通常 1e-4 以下不然太容易把预训练阶段学到的通用能力冲垮。这种做法适合两步走先领域续训让模型熟悉行业术语和表达习惯再 SFT 对齐具体任务格式。我做一个法律文书分类模型时就是这样先用几万份判决书做增量续训再用几千条分类指令做微调效果明显比直接拿通用模型做分类好。3.6 训练过程中的实时观察技巧训练不是把程序跑起来等结果你需要实时判断模型状态。第一看 loss 曲线的形状正常情况是平滑下降偶有小幅波动如果 loss 震荡剧烈、上下乱跳减学习率或者检查是否有个别超长样本干扰了梯度。第二看验证集 loss如果训练 loss 持续下降但验证 loss 回升就是过拟合信号要么减 epoch要么加数据增强或 dropout。有条件的话每隔几百步直接做一次小规模推理测试。我的做法是准备 5 到 10 条“金标准”测试问题训练时每隔 200 步就加载一次最新的 checkpoint看回答质量。这种方式能立刻发现灾难性遗忘或者风格偏移比只看 loss 数字可靠得多。训练任务跑完并不代表模型可用。4. 推理、合并与部署LoRA 权重怎么变成真正能用的东西4.1 把 LoRA 接到底座模型上做推理训练结束后output_dir 里会生成 checkpoint 目录里面保存的是 adapter_model.safetensors 和一个 adapter_config.json。加载 LoRA 模型做推理很简单import torch from transformers import AutoModelForCausalLM, AutoTokenizer from peft import PeftModel base_model_id Qwen/Qwen2.5-7B lora_path ./lora_qwen_7b/checkpoint-1000 tokenizer AutoTokenizer.from_pretrained(base_model_id, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( base_model_id, torch_dtypetorch.bfloat16, device_mapauto, trust_remote_codeTrue, ) model PeftModel.from_pretrained(model, lora_path) messages [{role: user, content: 你们公司的退货政策是什么}] input_ids tokenizer.apply_chat_template( messages, tokenizeTrue, add_generation_promptTrue, return_tensorspt ).to(cuda) outputs model.generate( input_idsinput_ids, max_new_tokens512, temperature0.7, top_p0.9, do_sampleTrue, ) response tokenizer.decode(outputs[0][input_ids.shape[1]:], skip_special_tokensTrue) print(response)注意一个细节这里直接用 base_model 加载进 PeftModel不需要加载整个训练时的 checkpoint。这样显存占用就是 base 模型加 LoRA adapter 的小开销非常灵活。如果你训练时有多个 checkpoint 想对比也可以逐个挂载测试。4.2 合并 LoRA 权重到底座模型LoRA adapter 虽然方便切换但推理框架不一定都认识。生产环境里特别是用 vLLM、TGI 等高性能推理引擎的时候更推荐先把 LoRA 权重合并到底座模型里生成一个普通的完整模型权重文件。合并代码同样简洁from transformers import AutoModelForCausalLM from peft import PeftModel base_model_id Qwen/Qwen2.5-7B lora_path ./lora_qwen_7b/checkpoint-1000 model AutoModelForCausalLM.from_pretrained( base_model_id, torch_dtypetorch.bfloat16, device_mapcpu, trust_remote_codeTrue, ) model PeftModel.from_pretrained(model, lora_path) merged_model model.merge_and_unload() merged_model.save_pretrained(./merged_model_qwen_7b) tokenizer.save_pretrained(./merged_model_qwen_7b)这里我特意把 device_map 设成 cpu原因是为了避免合并过程中大模型来回搬运耗尽显存。merge_and_unload 会把 LoRA 分支的权重按照 alpha/r 缩放后写入原始权重矩阵然后移除 adapter 结构最终 save_pretrained 得到的就是一个干净的、可以直接被普通加载逻辑识别的模型。4.3 本地部署链路从 PEFT 到 Ollama 和 vLLM合并后的模型用途很广。如果你只是本地自用拿 transformers 直接加载就够了。你要是想跑一个稍微正式的本地服务两条主流路线一条是 vLLM吞吐量高适合 API 服务场景另一条是 Ollama适合消费级桌面部署和快速体验。vLLM 的启动方式很直观python -m vllm.entrypoints.openai.api_server \ --model ./merged_model_qwen_7b \ --served-model-name qwen-7b-lora \ --max-model-len 4096 \ --gpu-memory-utilization 0.85启动后就有了一个符合 OpenAI API 格式的本地接口可以直接接各类应用。如果你想把模型给没有 Python 环境的同事用可以考虑转成 GGUF 格式再导入 Ollama。GGUF 转换需要 llama.cpp 的 convert 脚本合并完成后执行python convert_hf_to_gguf.py ./merged_model_qwen_7b --outfile qwen-7b-lora.gguf然后创建 Ollama Modelfile指向这个 GGUF 文件ollama create 一下就能本地对话了。实际体验是合并后的模型通过 GGUF 量化到 Q4_K_M显存占用进一步下降在普通笔记本上都可能跑得动。这个流程很适合做小型项目交付或者给团队内非技术成员试用。5. 常见问题与排查技巧实录训练和部署过程中总会遇到一些让人崩溃的情形。我把这段时间亲测遇到的典型问题整理成一份速查表每个问题都附上排查思路方便你对照定位。5.1 训练不收敛loss 一直在高位震荡先区分是“完全不下降”还是“震荡不降”。完全不下降的排查顺序数据格式是不是错了模型能不能按你的模板理解输入输出tokenizer 有没有把 pad_token 设置为 eos_tokentarget_modules 是不是设错了如果模块名和模型不一致LoRA 实际没有生效只是白训学习率是不是太小了LoRA 用 1e-5 很可能无声无息。如果是震荡不降大概率是学习率偏高或者某些样本序列长度差异过大导致梯度不稳定。我做首次训练时遇到过 loss 在 2.3 左右疯狂抖动把 learning_rate 从 3e-4 降到 1.5e-4 后明显平滑。另外启用 gradient_clipping 是一个好习惯在 TrainingArguments 里设置 max_grad_norm1.0能显著提升训练稳定性。5.2 批量大小和梯度累积的平衡显存不够时第一个想到的是减小 batch size这没有错。但要注意梯度累积gradient_accumulation_steps可以弥补小 batch 带来的梯度估计噪声。比如 per_device_train_batch_size2梯度累积 8 步等效 batch size 就是 16。注意这里等效指的是梯度更新频率前向反向依然按 2 个样本一批做显存占用不会翻倍。我的实际经验是显存紧张时先砍 batch size 到 1 或 2再用梯度累积把等效批次拉到 16 或 32然后看 loss 是否平滑。如果等效 batch 已经很大但梯度仍然震荡再考虑调低学习率。批量调参的核心原则是尽量保持“等效 batch 规模”和“学习率”之间的比例合理不要小 batch 配大学习率。5.3 过拟合与灾难性遗忘的博弈数据量小是过拟合的首要原因。几百条数据很容易让模型死记硬背这时我会用两种手段第一减少 epochLoRA 在 500 条数据的任务上跑 1 个 epoch 往往比跑 3 个 epoch 效果好第二对数据做一定的改写增强比如同义替换、句式变换相当于人工扩充数据集。灾难性遗忘另一种表达是“模型变笨了”。你训练客服问答时它可能忘了通用知识。解决思路是数据混入通用语料比如在训练数据里掺 10% 到 20% 的通用指令数据这些数据可以从公开指令集里随机采样。另外 LoRA 本身参数量小对原模型的破坏远小于全量微调所以只要学习率不超过 3e-4一般不会出现严重的遗忘问题。如果连 5% 的通用数据都不想混那就尽量少训练几轮。5.4 模型下载经常超时失败怎么办Hugging Face 上的模型动辄几个 GB国内网络环境下从默认域名拉取经常超时。我的习惯是先设置镜像环境变量再执行下载export HF_ENDPOINThttps://hf-mirror.com huggingface-cli download Qwen/Qwen2.5-7B --local-dir ./qwen2.5-7b之后再从本地目录加载模型model_id ./qwen2.5-7b tokenizer AutoTokenizer.from_pretrained(model_id, trust_remote_codeTrue)带宽问题早解决后面所有流程都顺畅。另外模型下载下来后强烈建议保存到固定路径避免每次训练前都临时拉取。5.5 多轮对话训练后模型总“忘事”这个现象很典型模型能流畅回答单轮问题但一旦让它读历史消息它要么无视上文要么把历史重复一遍。原因通常是训练数据里没有足够的多轮上下文样本模型没学过“把历史信息当作前提”的推理模式。修复方法其实不复杂多构造一些需要跨轮次推理的样本。比如第一轮用户说“我想吃辣一点的火锅”第二轮说“那帮我看看哪家评分最高”那么第二轮的回答必须引用第一轮的前提。这种数据比例我一般建议至少占到总数据量的 30%纯单轮数据再多模型也不会自动获得多轮能力。5.6 推理结果崩坏、生成一堆重复内容训练时 loss 很低但推理时模型回复重复、怪诞大概率是解码参数问题。temperature 过高会把模型推向随机区反复横跳过低又容易陷入重复循环。我的常用范围是 temperature0.7、top_p0.9配合 repetition_penalty1.05 到 1.1。如果生成长回复适当减小 top_p效果更稳。如果解码参数调了半天没用再看训练数据检查样本里有没有大量重复片段比如同一句话在 output 里出现多次。模型会对训练分布内的高频模式特别敏感重复生成往往就是数据里重复模式的镜像。在这些问题之外我还要额外说一个“小样本快速验证法”。无论你的最终目标数据集有多大第一次跑通训练流程时先只取 200 条数据、训练 30 步目标是验证数据加载没有报错、模型能正常输出、结果能保存加载。这一步跑通你才真正拥有“训练完整模型”的入场券。很多人在大数据集上折腾半天最后发现是数据字段映射错了白白浪费时间。我个人在实际操作中最深的体会是LoRA 训练这件事代码只占两成数据和处理流程占八成。Hugging Face 已经把工程复杂度压到很低剩下的比拼全在数据质量和对模型行为的感知能力上。如果你准备拿它做垂直领域应用把精力优先倾斜到数据清洗和格式统一上收益一定比反复调 LoRA 参数大得多。最后再分享一个小技巧当你纠结某个超参数时不要上来就直接训完整数据集。用 5% 的数据做三组小实验分别对比学习率 1e-4、2e-4、3e-4看 200 步内的 loss 曲线基本就能确定量级。这个小习惯帮你省下的训练时间足够你再搭一套数据清洗流程了。
返回列表