
简介面向大模型应用开发者的LLaMA快速微调实战项目聚焦如何利用预训练语言模型完成问答、文本生成、机器翻译等具体任务的二次训练。内容覆盖环境设置、数据准备、模型加载、微调配置、模型训练、验证与测试、保存部署的完整链路源码内置训练脚本、数据处理函数与模型配置文件并有逐步引导的教程说明。资源包共340个文件以Python脚本、JSONL数据、Markdown教程、Shell脚本为主同时包含模型权重pt、JSON/YAML配置及少量音频图片样例整体约31.92MB目录结构清晰便于按模块检索。目前已有820人学习下载适合希望快速上手大模型微调的研究人员、算法工程师及高年级学生。通过该项目可掌握数据预处理、超参数调节、训练监控与排错要点积累实战经验为后续业务场景中的模型优化提供参考。1. 快速微调 LLaMA先想明白“快速”两个字如果你手里拿到一份“大模型微调-快速微调LLaMA实现”的项目包第一反应通常是找训练脚本、拉满显存、跑一晚上看 loss。但真正有价值的不是“跑通”而是你能否在短时间内把一个通用模型变成能回答领域问题的模型。很多初学者以为微调是给模型灌输新知识其实多数场景只是在修正回答顺序和表达习惯想明白这点比调参更关键。快速微调 LLaMA 的“快速”指的就是尽量少的数据、尽量低的算力、尽量短的时间完成一次能拿到业务效果的方向修正。常见做法是自写一套 PEFT 训练流程或借助 LlamaFactory 这类集成工具完成环境配置、模型微调、模型部署和效果展示两条路各有利弊。下面这套流程是我在单卡上反复跑过的顺序照着走一遍能让你少踩两三天坑。2. 微调选型为什么 LoRA 是快速微调 LLaMA 的默认答案“微调”这个词听上去简单但第一次动手的人通常会面临一个选择全量微调、LoRA、还是 QLoRA。这个选择直接决定你对显存和数据量的诉求也决定翻车概率。先算账再选路是快速微调的第一课。2.1 全量微调、LoRA 与 QLoRA算力账与效果账全量微调的意思是整个 7B 模型的全部权重都参与梯度更新。对 7B 模型来说即使用 FP16 存储权重仅模型本身就要 14GB梯度还要 14GBAdam 优化器的动量要再吃掉约 28GB激活值和中间变量另算。也就是说全量微调一个 7B 模型40GB 显存都会很紧张13B 以上基本要依赖多卡或 CPU offload。这也是为什么“单卡快速微调”这条路里全量微调天然出局。LoRA 的原则很朴素不动原始权重而是在 Transformer 层的 Q、K、V、O 投影边上挂一组低秩矩阵 B 和 A训练时只更新这组小矩阵。对 7B 模型可训练参数往往只有 0.1%~1%。这带来的好处不只是训练参数少、优化器状态只剩几十兆更关键的是 LoRA 天然自带“隔离”作用不会把原始模型的通用能力搅乱比全量微调更不容易出现灾难性遗忘。QLoRA 在 LoRA 基础上再往前一步把底座模型的权重先量化到 4bit以 NF4 格式存在显存里训练时只对 LoRA 部分反量化计算。这样同样一张 24GB 显卡LoRA 只能勉强装下 7BQLoRA 可以轻松处理 7B并留出余量去调序列长度和批大小。从效果上看QLoRA 和 LoRA 的差距通常小于 1 个百分点对绝大多数业务场景完全可以忽略。这里有个比较直观的理解方式大模型在预训练阶段已经学会了语言、逻辑和大量常识微调并不需要推倒重来而是在原有参数附近搜索一个更适合业务方向的局部解。LoRA 相当于限定模型“只能在一个低维子空间里移动”这个限制乍一听像是约束实际上却是最好的正则能有效挡住训练数据里那些噪声样本带来的越界更新。这也是为什么小数据量下 LoRA 比全量微调更稳。我把三个方向的差别整理成一张表方便你对照自己的资源做决定。对比维度全量微调LoRAQLoRA可训练参数占比100%0.1%~1%0.1%~1%7B 最低显存参考约 40GB 以上24GB 左右12GB~16GB训练速度慢快比 LoRA 慢一点最小数据量建议5 万条1 千条1 千条通用能力保留容易遗忘较好较好典型适用场景数据充足、领域差异大单卡快速迭代消费级显卡/小显存看到这里不要急着无脑选 QLoRA。我一般会先看手里数据量少于 1 万条直接 QLoRA 起步超过 5 万条且预算允许才值得考虑 LoRA 甚至全量微调。数据量不够的时候选更重的方案只是把过拟合和灾难性遗忘的风险加倍并不会让业务指标变好。2.2 快速微调的硬件与显存估值24G 显存到底够不够用显存够不够不是看显卡标称多少 G而是要一条一条算。以 QLoRA 训练 7B 模型为例底座权重用 4bit 量化后大概占 4GBLoRA 参数和优化器状态加起来不到 0.5GB剩下的开销集中在激活值上而激活值和批大小、序列长度强相关。一个粗略经验是24GB 显卡上7B 模型配 2048 序列长度、batch size 设为 4通常能压在 20GB 以内跑起来如果序列长度拉到 4096batch size 就得降到 2。你可以按这个表快速毛估7B 模型 QLoRA、序列长度 2048、batch size 2 时显存占用大约在 14~18GB 之间序列长度 4096 时占用会跳到 20~24GB。如果加入 gradient checkpointing显存还会再降 20% 左右代价是每个 step 多走一次反向传播训练速度变慢 10%~20%。这是典型的拿时间换显存适合 12GB 用户。如果你手里的显卡只有 12GB也不是完全不能做但要学会“压缩到极限”batch size 设成 1配合 gradient_accumulation_steps 凑够等效批大小序列长度控制在 1024 以内训练层数只挂 q_proj 和 v_proj 而不是四个投影层如果还不行就在 4bit 量化基础上再加 CPU offload把不参与计算的权重先放到内存里。当然offload 的量一旦超过几百 MB训练速度会明显下降所以只建议当作兜底方案。还有一个小细节很多初学者分不清“模型大小”和“显存占用”。7B 模型在未量化的 FP16 下权重文件是 14GB这不是显存占用而是模型体积。量化成 4bit 后大约 4GB运行时的显存占用还要叠加 KV cache 和激活值。因此看到别人说“24G 能跑 30B QLoRA”时那通常是把序列长度调得很短、batch size 压到 1 的结果并不代表你的业务场景也适用。2.3 用 LlamaFactory 还是自己写训练脚本做快速微调时我见过两类人一类坚持只用自己写的脚本结果在 DataLoader 和自定义 loss 上花了两天另一类只点 LlamaFactory 的 Web UI数据一换就报错完全不知道哪里出问题。我的判断是第一轮做 demo直接上 LlamaFactory 是性价比最高的选择它把环境配置、模型微调、模型部署、效果展示整个流程都收在统一的配置里对新手友好。但如果你打算把它用在生产环境或者需要改 prompt 格式和采样策略我建议至少手写一遍 PEFT 训练脚本。原因很简单LlamaFactory 把很多细节藏起来了比如训练数据格式、attention mask 的处理、标签是否对 padding 位置做了掩码这些一旦出问题它就成了黑匣子你只能删掉整个实验重来。反过来当你自己写过一遍数据整理和训练调用再去看 LlamaFactory 的配置项很多参数一眼就能对应上排错时的感觉完全不一样。另外不管用哪种方式数据的格式和 tokenizer 处理都必须自己先过一遍。我见过直接把中文 csv 喂给 LlamaFactory 然后一顿操作跑完结果模型输出大量“undefined”的情况原因是输入列名对不上模板字段框架把整行数据当成一个字符串塞进了 prompt模型只能硬编。这种情况和训练参数没关系纯粹是数据入口没把关。所以下面提供一个按场景选择路径的参考情况建议路径数据少于 5000快速验证QLoRA LlamaFactory数据 5000~50000需要定制逻辑QLoRA/LoRA 自写脚本数据大于 50000算力充足LoRA 或全量微调先说明结论如果你的项目包里带了现成源码不要盲目替换先把数据格式看明白再跑如果包里只有流程教程按下一章的步骤自己写一份训练流水线这套功夫不会被浪费。用同一份数据写脚本和用框架的最终效果不会差太多但前者会让你具备调优的能力。3. 从数据到训练脚本在单卡上跑通 LLaMA 微调的最小闭环选型定了接下来就是动手阶段。这个闭环由四段构成环境准备、数据整理、训练启动、权重合并。按顺序走一路踩到坑也知道往哪躲。3.1 准备环境依赖清单与版本组合快速微调最怕的不是模型大而是 Python 依赖互相打架。我自己的习惯是先用 conda 新建一个干净环境再统一装下面的依赖。以 Python 3.10 为例一套组合是conda create -n llm-finetune python3.10 -y conda activate llm-finetune pip install torch2.1.2 --index-url https://download.pytorch.org/whl/cu118 pip install transformers4.38.2 datasets accelerate peft0.8.2 bitsandbytes第一行把 PyTorch 装成适配 CUDA 11.8 的预编译版本第二行的 transformers 必须大于等于 4.36否则对 4bit 量化和 attention mask 的支持会差很多。peft 版本我固定在 0.8.2 附近bitsandbytes 建议装最新版因为 4bit 的反量化算子一直在修 bug旧版本在一些新显卡上直接报 CUDA error 的概率很高。装完之后先别急着往下走用下面这条命令确认 PyTorch 和显卡驱动真的通上了python -c import torch; print(torch.cuda.is_available()); print(torch.__version__)输出应该看到True和一个带cu118后缀的版本号。如果看到False大概率是 torch 装成了 CPU 版或者 conda 环境里残留了旧版本。先把环境删掉重装不要强行继续否则后面训练时每一步都可能出幺蛾子。这一步花五分钟能帮你省出后面两小时。3.2 把业务数据整理成指令微调格式数据格式是 LoRA 微调最容易被低估的环节。LLaMA 家族模型的指令微调通常把一条样本组织成“指令、输入、输出”三个字段Alpaca 格式是其中最常见的一种。下面是一条消防设备维保领域的示例{ instruction: 消防泵启动后压力表无读数可能是什么原因, input: 设备型号XBD7.0/10G-L工频启动后读数为零, output: 先检查压力表是否损坏排除后重点查泵内进水是否充足、叶轮是否堵塞、出口阀门是否打开。 }如果你手里的原始数据是几百行二维表比如“问题、背景、答案”可以直接用脚本转成上面的 JSON 格式。我一般会顺手做两件事一是统一角色标记给每条样本加系统提示词二是做训练集和验证集的切分按 9:1 随机切分避免模型在验证阶段“背答案”。import json, random, pandas as pd # 假设原始数据是 Excel 或 CSV列名为 question, context, answer df pd.read_csv(domain_data.csv) random.seed(42) train, valid [], [] for _, row in df.iterrows(): item { instruction: row[question].strip(), input: row[context].strip(), output: row[answer].strip(), } if random.random() 0.9: train.append(item) else: valid.append(item) with open(train.json, w, encodingutf-8) as f: json.dump(train, f, ensure_asciiFalse, indent2) with open(valid.json, w, encodingutf-8) as f: json.dump(valid, f, ensure_asciiFalse, indent2) print(ftrain{len(train)}, valid{len(valid)})这段脚本的逻辑很简单但有三个细节值得说明。ensure_asciiFalse保证中文能被直接读出来而不是被转成\uXXXX序列strip()清理首尾空格能避免模型把换行符当正文学进去随机种子固定为 42 是为了复现实验结果否则每次切分不同模型效果对比就没了基准。还要注意一个实际经验如果原始问答里包含表格、图片、超长日志不要直接塞进input字段。LLaMA 的分词器对超长文本的处理质量有限序列长度一上去训练速度就断崖式下跌。超过 2048 字符的样本我通常会拆成多条短样本或者先做字段精简把“答案”里的格式模板保留把无意义的口水话去掉。3.3 核心训练脚本与关键参数说明环境有了数据有了接着就是训练脚本。下面这段脚本是我在 24GB 显存单卡上跑 7B QLoRA 的最小可运行版本代码里每一步都标注了用途import torch from transformers import ( AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer ) from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training from datasets import load_dataset model_path your_local_path/llama-2-7b-hf # 也可换成 qwen2.5-7b 等开源模型 tokenizer AutoTokenizer.from_pretrained(model_path) tokenizer.pad_token tokenizer.eos_token # LLaMA 没有 pad_token必须手动指定 model AutoModelForCausalLM.from_pretrained( model_path, load_in_4bitTrue, # 4bit 量化显存占用骤降 torch_dtypetorch.bfloat16, device_mapauto, use_cacheFalse, # 训练时关闭 KV cache省显存 ) model prepare_model_for_kbit_training(model) # 冻结原权重为量化准备 lora_config LoraConfig( r8, # 低秩矩阵的秩 lora_alpha32, # 缩放系数一般取 r 的 2~4 倍 lora_dropout0.05, target_modules[q_proj, k_proj, v_proj, o_proj], task_typeCAUSAL_LM, ) model get_peft_model(model, lora_config) model.print_trainable_parameters() # 确认可训练参数占比 def format_fn(examples): texts [] for instruction, input_text, output in zip( examples[instruction], examples[input], examples[output] ): prompt f### 指令{instruction}\n### 输入{input_text}\n### 回复 texts.append(prompt output tokenizer.eos_token) return {text: texts} dataset load_dataset(json, data_files{train: train.json, validation: valid.json}) tokenized_dataset dataset.map( lambda x: tokenizer(format_fn(x)[text], truncationTrue, max_length2048, paddingFalse), batchedTrue, remove_columnsdataset[train].column_names, ) training_args TrainingArguments( output_dir./lora-checkpoint, per_device_train_batch_size2, gradient_accumulation_steps8, num_train_epochs3, learning_rate2e-4, bf16True, logging_steps20, save_steps200, save_total_limit2, evaluation_strategysteps, eval_steps200, ) trainer Trainer( modelmodel, argstraining_args, train_datasettokenized_dataset[train], eval_datasettokenized_dataset[validation], ) trainer.train()先把逻辑说清楚加载模型时用load_in_4bitTrue完成量化prepare_model_for_kbit_training会冻结模型权重并为低秩适配器腾出训练位置get_peft_model把可训练矩阵挂进去。数据侧format_fn把每一条样本拼成完整的输入输出文本再统一做分词和截断。之后所有逻辑交给 HuggingFace Trainer 处理。参数选择上r8是 LoRA 的秩秩越高能学到的信息越多但随之而来过拟合和显存占用也越高。lora_alpha32是缩放系数实际生效的缩放比例大致是alpha / r 4这个比例不宜调得太大否则模型权重更新过猛输出会变得暴躁。learning_rate2e-4是经验值对 7B 模型来说超过 5e-4 很容易在几百步内把 loss 打到 NaN。batch_size2加gradient_accumulation_steps8等效出了全局批大小 16这个数值偏保守但胜在稳定。注意训练前建议先打印两三条 tokenized_dataset 样本确认 prompt 模板和标签是否拼接正确。这一步能拦住大部分“loss 正常但输出乱码”的问题不要跳过。训练过程中的 loss 曲线不用过分在意绝对值重点看验证集 loss 是否随训练步数同步下降。如果训练 loss 一路降、验证 loss 却突然抬头说明模型开始死记硬背训练集了可以提前终止训练。3.4 训练完合并 LoRA 权重保存与加载训练结束后训练器默认保存的是 LoRA 适配器权重训练产物体积只有几百 MB。但部署和后续推理时每次都要先加载底座模型再加载适配器既麻烦又容易出维度错误。所以我把“合并权重”当成标准动作import torch from transformers import AutoModelForCausalLM from peft import PeftModel base_model_path your_local_path/llama-2-7b-hf lora_path ./lora-checkpoint/checkpoint-xxx # 按实际保存步数修改 base_model AutoModelForCausalLM.from_pretrained( base_model_path, torch_dtypetorch.bfloat16, device_mapauto, ) merged_model PeftModel.from_pretrained(base_model, lora_path).merge_and_unload() merged_model.save_pretrained(./merged-model)这段脚本的关键在merge_and_unload()它会把 LoRA 的低秩矩阵和底座权重现场做矩阵加法拼成一个完整的可用模型。保存后./merged-model的体积和底座模型一样大可以直接交给后续部署脚本或量化工具处理。注意加载时要用和训练时一致的torch_dtype否则合并出来的权重在精度上会和训练时有细微偏差。有几点我得提醒你。合并前一定要确认lora_path指向的那个 checkpoint 是你手动挑选的结果而不是最后一个自动保存点。自动保存的 checkpoint 偶尔会掉在过拟合段效果往往不是最好的。另外不要直接在原目录执行merge_and_unload后又保存回原目录先把合并结果存成一个新目录避免覆盖原始权重。4. 效果评估微调完的 LLaMA 是变好了还是变傻了训练跑完训练日志里显示 loss 降到了 0.8这不能说明模型合格。LLaMA 这类生成模型在训练集上把 loss 压到很低很容易真正难的是面对没见过的业务问题时仍然稳定。下面这套评估流程我每次都会跑一遍。4.1 面向任务的自动化评估用留出集算分自动评估的第一步是先把验证集捡回来。前面切分数据时留出的 10% 验证集就是用来回答“模型是否学到了规律”的。最省事的办法是复用 Trainer 的 evaluate 方法看一下预测 losstrainer.evaluate(eval_datasettokenized_dataset[validation])这个 evaluate 会输出 eval_loss 和 eval_runtime 两个字段eval_loss 越低说明模型在验证集上的困惑度越小。但千万别只盯着它看它反映的是语言建模层面的概率不能直接代表业务正确率。比如消防维保场景里模型把“检查出口阀门是否打开”说成“检查进口阀门是否打开”loss 可能相差无几但业务上完全不可接受。对于生成任务我可以按类别手工定义规则。以“故障排查建议”任务为例把答案里必须出现的领域关键词抽出来比如“叶轮”“压力表”“出水阀”“排气阀”然后让模型生成答案用关键词命中率来衡量质量import json from transformers import AutoModelForCausalLM, AutoTokenizer model_path ./merged-model tokenizer AutoTokenizer.from_pretrained(model_path) model AutoModelForCausalLM.from_pretrained(model_path, torch_dtypetorch.bfloat16, device_mapauto) def generate_reply(prompt, max_new_tokens256): inputs tokenizer(prompt, return_tensorspt).to(cuda) outputs model.generate( **inputs, max_new_tokensmax_new_tokens, do_sampleFalse, repetition_penalty1.05, ) return tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokensTrue) def keyword_hit_rate(sample, reply): needed sample[keywords] # 验证集里预先标注好期望关键词 hit sum(1 for k in needed if k in reply) return hit / len(needed) with open(valid.json, r, encodingutf-8) as f: valid_data json.load(f) total 0 for sample in valid_data[:50]: # 先跑 50 条观察分布 prompt f### 指令{sample[instruction]}\n### 输入{sample[input]}\n### 回复 reply generate_reply(prompt) total keyword_hit_rate(sample, reply) print(faverage keyword hit rate: {total / 50:.2f})这个脚本的意义不是给你一个终极指标而是当你改动数据或调整参数后能拿同一套规则回头对比。关键词命中率的绝对值高低不重要重要的是它在多次实验之间如何变化。如果模型把所有领域词汇都堆在回答里这个指标会虚高所以我会额外人工抽查而不是只看一个分数。注意do_sampleFalse是故意关掉采样的这样同一份 prompt 每次生成都完全一样评估才可复现。4.2 面向业务的评审微调前后对比与坏例收集自动分数筛掉明显不合格的版本后剩下的要靠人来验收。我会把微调前的基础模型和微调后的模型放在同一个业务测试集上对比专门挑 10 个高频真实场景逐一观察两个模型的回答差异。这步虽然费时却是快速微调实践里最容易被跳过、也最致命的一环。在做微调前后对比时我习惯把两个模型的输出都折叠到标准格式里再让一个更懂业务的同事盲评。盲评很重要因为只要你心里有预期就很容易给微调后的版本打高分。每次盲评的对照表保留下来下一次调参后直接覆盖同一张表进步和退步都一目了然。一个典型的对比表格长这样场景基础模型回答微调后回答结论消防泵压力表无读数先检查电源是否正常先确认压力表是否损坏再排查泵内进水和叶轮可用喷淋系统末端试水压力偏低建议更换水泵先放水观察压力变化检查管路是否有气锁需人工复核对比做多了会总结出一个规律基础模型不是不会说行业术语而是不知道故障排检的优先级。微调后的提升往往体现在“把正确的检查顺序说出来”而不是记住更多名词。如果发现微调后模型连基础知识都答错了那不是数据量的问题需要回到上一章检查数据里是否有大量噪声或标签错误。每次评审时记得把“坏例”单独收集到一个 json 文件里。这些坏例是后续迭代最有用的素材不要只在脑子里留个印象。整理坏例时记录四个字段原始 prompt、模型回答、期望回答、你觉得模型错在哪。下一轮微调时把坏例按一定比例混入训练集模型很快就能修正同类问题。这比加几千条新数据有效坏例就是留给自己的后悔药。4.3 评估失败的定位从数据、训练参数到模型版本要是新模型在评估集上的表现不如上一版别急着改数据先按“数据 → 训练 → 推理”的顺序排查。数据侧看两处验证集是不是和训练集有重叠重叠会让评估结果虚高prompt 模板在训练和推理时是否一致比如训练时“### 回复”后面直接跟答案推理时却多打了一个换行效果会肉眼可见地下降。训练侧主要看可训练参数规模和步数。LoRA 秩从 8 提到 32 后通常需要同步降低学习率否则模型在训练后期容易振荡。推理侧则要看生成参数do_sampleTrue和temperature0.9会引入随机性两次评估结果相差大时请先固定随机种子和采样策略。如果以上排查都做了还是退步还有一个很现实的原因新版本数据里混入了格式不整齐的样本。比如有些训练样本的“输出”字段里带着“答”、“回复”这类前缀模型会把这种前缀当成回复部分学进去推理时就会多输出一段无意义引导语。这类问题在自动评分里很难发现只能靠人工看坏例来定位。5. 微调 LLaMA 踩坑记录五个高频问题与排查步骤做快速微调时踩过的坑比微调本身教给我的更多。下面这五个问题出现的频率最高每条都按“现象 → 原因 → 解决”来拆希望能替你把路铺平一点。5.1 训练中 loss 变成 NaN 或突然冲上天现象训练到第 200 步左右日志里 loss 从 1.2 变成nan之后无法恢复。原因最常见的是学习率过大。为了让模型快速收敛把 learning_rate 设成 5e-4 甚至 1e-3结果 7B 模型的更新步长太大梯度爆炸后数值直接溢出。另外旧显卡跑 bf16 时如果底层实现不够稳也可能在长时间训练后出现精度异常。还有一类隐蔽原因是数据里有超长文本截断后标签全部落到 padding 上导致损失计算出现除零。解决先把学习率降到 2e-4并在 TrainingArguments 里加gradient_clip_val1.0。再把数据里超过 1536 字的样本筛出来人工看一眼确认output字段非空且不是一句废话。如果问题依旧跑一版纯 FP16 训练做对照确认是不是 bf16 的锅。按这个顺序排查绝大多数 loss 翻车都能被定位到。5.2 loss 降了但业务指标没涨LoRA 配置与数据的锅现象训练结束时训练 loss 掉到 0.6但跑到验证集上领域问题的回答和基础模型差别不大该给的操作步骤还是错的。原因这个现象太典型了。一是 LoRA 的秩设置过低r4只能学到表层措辞变化学不会领域知识二是训练数据里“指令”的描述太单一比如所有问题都长一个样模型只记住了模板三是数据集规模不足几百条样本还编不出稳定的映射关系。解决把r提到 16 或 32同时把lora_alpha提到 64效果会更明显。数据侧把训练样本里的“输入”字段扩一下哪怕同一条答案配上 3 种不同的问题表述也比硬凑 3000 条同质数据有用。还有一个细节检查训练数据里是否大量重复了同一条业务样例重复样本权重过大会把 LoRA 的更新方向带偏。5.3 QLoRA 量化工具在启动时报错现象脚本一跑起来控制台报bitsandbytes相关的 CUDA error或者在from_pretrained里直接提示 unknown quantization layer。原因绝大多数时候是 bitsandbytes 的预编译扩展和本机 CUDA 版本不匹配。比如机器里 CUDA 12.1但 torch 装的是 cu118bitsandbytes 加载时找不到对应算子。还有一类是 transformers 版本过旧对 4bit 反量化层的解析方式还在更新旧版本不兼容新轮子。解决先用pip install -U bitsandbytes升级到最新然后确认torch.version.cuda输出与 PyTorch 编译时的 CUDA 版本一致。如果不一致要么把 torch 换成匹配的版本要么在环境变量里指定兼容模式。这类问题几乎都是环境问题和模型本身没有关系别反复删 checkpoint先把 CUDA 版本对齐。5.4 推理时输出全是重复文本现象模型生成“好的我可以帮您检查消防泵压力表。可以帮您检查消防泵压力表。可以帮您检查消防泵压力表……”然后死循环。原因两个原因都常见。一是训练阶段没有对 padding token 做掩码模型学到可以用 padding 区域生成无意义内容二是推理时把max_new_tokens设得很大而repetition_penalty又没开模型在编不出内容时就不断复读自己刚输出的片段。前者是训练 bug后者是推理参数问题。解决训练侧在分词后把labels中 padding 位置设为 -100让损失函数忽略这些位置推理侧设置repetition_penalty1.1并且把do_sample设为 False或者把 temperature 调低到 0.3 以下。改掉这两个点之后复读问题基本能根除。5.5 合并后的模型和训练时行为不一致现象训练时验证集上回答质量不错但把 LoRA 合并回底座模型后跑同一个 prompt 得到的回答变得很差甚至多出前导符号。原因合并脚本和训练脚本在精度上不一致。训练时用的是 4bit 量化底座加 LoRA合并时如果用 FP32 去加载底座权重低秩矩阵叠加后数值分布发生轻微偏移还有一类是tokenizer.decode时没有跳过特殊 token导致输出前面挂着s和###这些符号。解决合并时用和训练完全相同的加载方式保持torch_dtypetorch.bfloat16合并前先打印一遍model.dtype确认。合并后做一次对照测试用同一批 10 条 prompt 分别跑训练时的 checkpoint 和合并保存后的模型如果输出不一致再回来查加载参数。这一步能救回你至少一个下午。6. 把微调成果接进业务部署验证与量化取舍微调完成后业务方要的不是 checkpoint 文件而是一个能对话的接口或一个能跑在边缘设备上的模型。这最后一段路重点是验证和取舍。如果显卡资源允许最简单的部署是直接加载合并后的模型用 FastAPI 包一层。脚本逻辑很简单启动时加载模型收到请求后拼 prompt 调generate完成后返回文本。这样做的优点是零转换成本缺点是 7B 模型在推理时仍要占 14GB 显存如果有并发请求还得靠排队解决。所以拿 demo 给业务方看时可以这么做真正要交付时我一般会再做一步量化用 GGUF 格式把模型压到 4bit让它在普通 CPU 机器或小显存设备上也能跑llama.cpp 就原生支持这种格式。这一步会让回答质量有一点损耗但换来的是人人都能用服务器跑模型。验证环节别忘了一个细节量化后的模型一定要跑一遍第 4 章的评估脚本对比量化前后的关键词命中率。如果量化后指标掉得不多就直接交付如果掉得明显退回 6bit 量化或保留半精度。这种“先量化再验证”的动作比任何口头承诺都有说服力。提示部署前先确认模型目录下是否包含 tokenizer 文件否则推理时会直接报 tokenizer not found找半天才反应过来是文件没拷全。我现在把流程固定成了模板每次接手新项目都会直接复用先拿 100 条标杆数据做微调跑通后再扩展到全量数据评估脚本里始终保留 10 条固定场景用来做版本之间的回归对比。这个习惯帮我在很多次模型迭代里及时踩了刹车避免把一个明明更差的版本当成成果交出去。希望这个流程能帮到你让你做 LLaMA 微调时少走我走过的弯路。本文还有配套的精品资源点击获取