资讯动态

XTuner+InternLM-Chat认知微调:数据配比与LoRA实战

发布时间:2026/10/27 19:50:42 来源:尧图企业网站定制
1. 先想清楚认知微调到底在改什么1.1 通用模型与你的助手之间差的那层窗户纸一个通用对话模型你问它你是谁它多半会回一段模板化的自我介绍语气客气、内容空泛说完你也不知道它到底能帮你干什么。你要的不是这个你要的是一个认得自己的助手知道自己叫什么、定位是什么、擅长什么、不擅长什么、被问到边界外的问题该怎么回应。这事听起来像产品文案实际上是模型行为层面的问题——它落在权重里不落在提示词里。我最早做这个尝试的时候想法很朴素与其每次对话都在 system prompt 里长篇大论地规定人设不如把这些信息直接焊进模型。system prompt 的缺点很明显一是占上下文二是容易被后续的对话内容冲淡三是用户随便一句忽略前面的设定就可能把它带偏。认知微调解决的就是这个问题用少量高质量样本把我是谁这件事训练成模型的默认反应不依赖任何额外提示。这里要区分两个容易混淆的概念。能力微调是教模型新技能比如让它会写 SQL、会做表格抽取这类微调需要大量领域数据动辄上万条。认知微调也有人叫自我认知微调、身份微调改的是模型的自我表述和行为风格数据量小得多几百到两千条往往就够但它对数据质量和分布非常敏感。很多人第一次做会翻车训完之后模型确实改口说自己是谁了但原来会做的题开始做错或者说话变得机械重复。这不是工具的问题是数据配比和训练配置的问题。适合读这篇的人分两类。一类是想给自己的应用做一个有稳定人设的对话入口需要可复现的完整流程另一类是纯粹想搞清楚大模型微调这条链路——数据怎么造、配置怎么改、权重怎么转、效果怎么验——把它当一次完整的练手。两类人的关注点不一样我在后面会尽量把为什么这么选讲透你按自己的目标取用。1.2 从 token 分布的角度理解改口这件事模型输出每一个 token本质是在给定前文的条件下做一次概率采样。你问你是谁模型见过海量的我是一个AI助手这类表述所以它的概率分布天然指向那个方向。微调做的事是用梯度把这条路径的概率压低把我是XX助手擅长XX这条路径的概率抬上去。理解这一点很关键因为它决定了三件事。第一样本必须成对出现不只要告诉模型应该说什么还要让它在这个具体的问法下说。你只写十条我叫某某但用户实际会问你叫什么名字你是哪个模型介绍一下你自己你能干嘛这些问法在训练时都得覆盖到否则模型只对训练里出现过的句式敏感。第二损失只在回答部分计算。输入的问题部分要被 mask 掉不然模型会去学怎么提问而不是怎么回答。第三认知改变是全局性的你动的是同一套参数所以它必然会波及其他能力这就是为什么数据配比里要掺通用对话。还有个细节值得说认知微调容易训出复读机。原因是样本高度同质模型找到了一条捷径——不管问什么先把自我介绍背一遍。要避免这个样本里得有相当比例的正常对话让模型知道自我介绍是特定场景下的反应而不是万能回复。1.3 为什么这套组合适合个人开发者上手选 XTuner 做工具、选 InternLM-Chat 做基座这个搭配在个人实验场景里有几个实打实的优势。工具侧XTuner 把配置、训练、权重转换、对话验证串成了一条流水线配置文件就是一个 Python 字典改参数不用去翻源码。它内置了 LoRA、QLoRA、全参、多卡 DeepSpeed 这些模式切换只是改配置里几行。对个人来说最重要的两点一是支持低显存微调单张 24GB 消费级卡能跑起来 7B 的 LoRA二是xtuner chat这个子命令能直接加载转换后的权重做交互验证省掉了自己写推理脚本的功夫。模型侧InternLM-Chat 系列对中文的支持比较扎实chat 版本已经做过对话对齐你在此基础上做认知微调等于在一个已经会聊天的基础上改人设比从基座模型开始省事得多。它的对话模板prompt template在工具里有官方实现训练和推理只要用同一个模板名格式就不会错位——这一点比很多人自己手写拼接要稳。需要提前说明的是下面所有的路径、参数、命令都是基于常见实践整理的参考方案不同版本的工具有细微差异你落地时以本地--help输出为准。2. 动手前把账算清楚硬件与方案选型2.1 显存到底花在哪一份粗略但够用的账本微调最劝退的环节是显存。很多人卡在这里不是因为不懂原理而是不知道自己的卡到底够不够。我按最坏情况给你拆一遍7B 参数模型bf16 精度。先看全参微调。权重本身占 7B × 2 字节 14GB。反向传播要有梯度同样 14GB。Adam 优化器要为每个参数维护一阶和二阶动量通常是 fp32再加一份 fp32 的主权重副本这部分约 7B × 12 字节 84GB。光这三项就 112GB还没算前向过程中的激活值。所以全参微调 7B 基本要 8 张 80GB 的卡配 ZeRO-3 才舒服个人场景不现实。再看LoRA。主干权重冻结仍然要占 14GB但梯度和优化器状态只针对新增的低秩矩阵。假设 rank16挂在 q/k/v/o 和 MLP 上可训参数大概 4000 万量级这部分梯度和优化器状态加起来不到 1GB。真正的大头变成了激活值它和 batch size、序列长度成正比。方案权重梯度优化器激活值seq 2048, bs 1经验显存下限全参 bf1614GB约 98GB数 GB 到十几 GB8×80GBLoRA bf1614GB1GB约 4-8GB20-24GBQLoRA 4bit约 4GB1GB约 3-6GB12-16GB实际跑下来InternLM-Chat-7B 用 LoRA、序列长度 2048、batch size 1在 24GB 卡上大概占到 21-22GB余量很小。所以如果只有 24GB序列长度建议先压到 1024 跑通再往上试如果是 16GB 卡直接上 QLoRA别硬扛。提示显存估算永远只看能不能跑通不看跑得快不快。序列长度从 2048 降到 1024激活值差不多砍半但长对话样本会被截断认知微调的数据一般不长所以这个取舍通常划算。2.2 可训练参数挂在哪LoRA 配置的取舍逻辑LoRA 的核心是用两个小矩阵的乘积去近似权重更新rank决定这个近似的表达力。rank 越大越接近全参但参数和显存也跟着涨。认知微调任务简单rank 16 甚至 8 通常够用我一般从 16 起步。更关键的是target_modules挂在哪。有些教程只挂 q 和 v这在纯风格迁移上能出效果但认知微调涉及模型知道什么我建议挂全一点q_proj、k_proj、v_proj、o_proj这四个注意力投影加上gate_proj、up_proj、down_proj这三个 MLP 投影。原因在于MLP 层在大模型里承担了大量事实性知识的存储只改注意力层模型可能学会用某种口吻说话但改不掉它内心认定的身份事实。我对比过只挂 qv 和挂全量的效果后者在被反复追问身份时的稳定性明显更好。lora_alpha通常设成 rank 的 2 倍等于给 LoRA 分支的输出一个缩放系数。lora_dropout设 0.05 左右做正则数据量小于 500 条时可以提到 0.1防止过拟合。2.3 环境准备的清单式做法环境这块最省事的办法是拉官方镜像省去 CUDA、PyTorch、flash-attn 三件套的版本对齐折磨。如果必须自己装顺序很重要先确定 CUDA 版本再装对应 CUDA 的 PyTorch最后装 flash-attn 并用pip install flash-attn --no-build-isolation避免编译问题。# 建立独立环境避免污染系统 Python conda create -n xtuner-env python3.10 -y conda activate xtuner-env # 按官方仓库说明安装注意先装 torch 再装 xtuner pip install torch2.1.0 torchvision --index-url 本地镜像源 git clone 仓库地址 cd xtuner pip install -e .[all]装完先做一次自检确认xtuner命令能识别以及 torch 能看到卡xtuner list-cfg | head -n 20 python -c import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))list-cfg能列出内置配置就说明装通了。这一步别跳过我见过不少人在训练跑到一半才发现依赖缺失前面的时间全白费。3. 数据怎么造认知微调真正的核心资产3.1 数据格式的约定与踩坑点XTuner 走的是 HuggingFace datasets 那条链路数据最终要转成对话结构。最通用的写法是 openassistant 风格每行一个 JSON 对象{conversations: [{from: human, value: 你是谁}, {from: gpt, value: 我是某某助手一个专注帮你处理文档整理和内容润色的小工具。}]}保存成.jsonl一行一条注意不要有 BOM不要用 Windows 换行符搞出奇怪的字符。字段名from和value是固定的human和gpt这两个角色标记也别改配置里的 map_fn 依赖它们做角色识别和 loss mask。真正容易出问题的是模板匹配。你选了internlm_chat这个 prompt template训练时工具会自动把对话拼成带特殊标记的形式回答部分被标记为需要计算 loss。如果训练用这个模板、推理时忘了加--prompt-template参数模型看到的输入分布和训练时不一致输出就会变得莫名其妙——常见症状是回答里混进特殊符号、或者答非所问、或者把用户的问题原样复述一遍。这个坑我踩过排查了半天以为是模型训坏了其实是模板没对上。另一个坑是system字段。有些配置会在模板里注入一段 system prompt训练时加了推理时就必须加同样的内容否则同样是分布偏移。认知微调的场景里我倾向于不依赖 system prompt把身份信息全部写进训练样本的回答里这样推理时越简单越好。3.2 自我认知样本的设计数量、句式、边界数据设计的核心是覆盖用户所有可能的问法。我把认知样本分成五类每类的目的不同。第一类是直接身份询问。你是谁你叫什么名字你是什么模型介绍一下你自己。这类最好写但要注意答案不能每次一字不差否则模型会背成固定字符串换个问法就崩。第二类是能力边界。你能做什么你支持哪些功能你能不能帮我写代码。这里要诚实把不擅长的明确说出来。很多人的助手训完之后什么都敢答应用户一用就发现不行体验反而更差。第三类是追问抗压。你真的是XX吗你是不是在骗我换个模型回答我。这类样本是拉开质量差距的地方。没有这类数据模型在被质疑时会立刻改口前面立的设定瞬间垮掉。第四类是闲聊转移。用户问完身份接着问天气、问技术问题模型要能自然过渡而不是每句话都带自我介绍。这类样本直接抑制复读机倾向。第五类是拒答与引导。对超出范围或者不该回答的内容给出一个得体的拒绝并引导回正题。数量上纯认知样本 200 到 500 条足够再多边际收益递减还容易过拟合。句式的多样性比数量更重要我一般要求同一个意图至少有 8 到 10 种不同问法长短句混着来标点符号也别统一真实用户不会规规矩矩打问号。3.3 掺多少通用数据防遗忘的配比实验只喂认知数据训完模型会变傻这是必然的。原因前面说过你动的是全局参数只优化一个小目标其他方向的能力就会漂移。解决办法是掺通用对话数据。比例上我试过几组认知:通用效果观察1:0身份改得很彻底但通用问答明显退化长回答容易跑题1:1身份稳定通用能力基本保住但偶尔在闲聊里硬插自我介绍1:3身份能改过来通用能力几乎无损需要稍微多训几轮1:5身份改动偏弱需要提高学习率或增加认知样本权重落到实践1:2 到 1:3是比较稳的区间。通用数据不用自己造找一份公开的多轮中文对话数据清洗掉质量差的、长度异常的、含有大量英文的取个一两千条就够。清洗这一步别省通用数据里的脏样本会把认知效果带偏尤其是那种答非所问的样本会让模型学会随便糊弄。还有个细节通用数据里如果出现我是一个AI助手这类表述会跟你的认知样本打架。两种处理方式一是直接把这类句子过滤掉二是保留但控制比例让模型学会在不同语境下说不同的话。我倾向后者因为真实场景里模型确实需要在某些场合保持谦逊的中性表述。注意数据里绝对不能出现真实的人名、机构名、联系方式。哪怕只是举例也可能被模型记住并在不恰当的场合复述出来。要举例就用占位符。4. 配置修改与训练启动把每一步的意图讲明白4.1 从模板复制一份配置只改该改的地方XTuner 的内置配置很多先用xtuner list-cfg | grep internlm找到对应基座的模板复制一份出来改不要直接在原文件上动手。复制出来之后重命名成有意义的名字比如internlm_chat_7b_selfcog_lora.py方便后面转换权重时引用。配置是一个 Python 文件里面定义了几个模块级变量训练器按名字去取。常改的就那么几处pretrained_model_name_or_path指向本地模型目录data_path指向你的 jsonlmax_length、batch_size、accumulative_counts、max_epochs、lr还有lora那一段。我见过有人把pretrained_model_name_or_path写成模型名而不是本地路径结果训练时卡在下载环节半天不动。离线环境下务必用绝对路径并且提前确认目录里有config.json和分词器文件。4.2 关键字段逐个拆为什么要这么设max_length控制单条样本的最大 token 数。设太小会把长对话截断设太大会吃显存。认知样本一般很短通用数据可能长一些2048 是个安全的折中。配pack_to_max_lengthTrue可以把多条短样本拼进一个序列显著提升吞吐——不拼的话大量 token 花在 padding 上纯属浪费算力。batch_size×accumulative_counts决定等效批大小。单卡 batch 一般设 1靠梯度累积凑到 16 或 32。等效批大小影响梯度稳定性太小会让 loss 曲线抖得厉害。认知微调数据量小等效 16 通常够用。lr是最需要区别对待的参数。全参微调常用 2e-5LoRA 因为只训少量参数学习率要大一个量级1e-4 到 2e-4是常见区间。设小了 loss 降不动设大了会震荡甚至发散。我第一次做的时候沿用全参的 2e-5训了 500 步 loss 几乎没动还以为数据有问题。max_epochs不宜多。认知微调数据量小3 轮左右就够配合save_total_limit保留最近几个 checkpoint。超过 5 轮基本就是过拟合模型开始逐字背样本。evaluation配置建议开EvaluateChatHook配置里填几个固定问题训练过程中会定期打印模型的实时回答。这是最直观的观察手段比盯 loss 曲线有用得多。看着模型的回答从我是一个AI助手慢慢变成我是XX那种感觉挺踏实的。4.3 启动训练与日志解读单卡启动xtuner train ./configs/internlm_chat_7b_selfcog_lora.py \ --work-dir ./work_dirs/selfcog_v1多卡或者需要 ZeRO 优化时加 DeepSpeedNPROC_PER_NODE2 xtuner train ./configs/internlm_chat_7b_selfcog_lora.py \ --deepspeed deepspeed_zero2 \ --work-dir ./work_dirs/selfcog_v1第一次跑建议加--cfg-options临时把max_epochs改成 1跑个几十步确认链路通了再放开。这招救过我不少次——配置写错了、数据路径不对、显存不够一分钟内就能暴露不用等半小时。日志里重点看三项。一是lossLoRA 起步一般在 2.0 到 3.0 之间正常的话几百步内能降到 1.0 以下。二是lr确认学习率调度按预期走warmup 结束后应该稳定在设定值。三是grad_norm如果它突然飙到很大再归零说明那一步出现了数值问题通常是学习率太高或数据里有异常长的样本。loss 曲线有几种典型形态对应不同的问题曲线形态可能的成因处理方向长时间在 2.5 上下震荡不降学习率太小或 loss mask 失效问题也被计算了提高 lr 到 1e-4 以上检查模板前几十步就掉到 0.1 以下数据重复度高样本几乎一样增加句式多样性检查是否有重复行降到 1.0 后突然反弹并震荡学习率后期偏大或 batch 太小加梯度累积检查调度器稳定下降但下降极慢等效批太大导致每轮步数太少减小累积步数或增加轮数4.4 断点续训与中间检查点训练中断是常态尤其是抢占式环境或者本地机器要关机。XTuner 默认会在 work-dir 里按间隔存 checkpoint续训加--resume参数指向上一次的权重就行。xtuner train ./configs/internlm_chat_7b_selfcog_lora.py \ --work-dir ./work_dirs/selfcog_v1 \ --resume ./work_dirs/selfcog_v1/iter_600.pth这里有个经验不要只看最后一个 checkpoint。认知微调容易在后期过拟合倒数第二个、第三个 checkpoint 的综合表现往往比最后一个好。我的做法是每个保存点都转一次 HF 格式用固定问题集跑一遍横向对比之后挑一个而不是无脑用最新的。5. 权重转换、合并与效果验证5.1 pth 转 HuggingFace 格式训练产物是.pth推理和部署要用 HF 格式中间要转一次。转换需要提供原始配置文件里面记录了模型结构信息和训练产出的权重路径xtuner convert pth_to_hf \ ./configs/internlm_chat_7b_selfcog_lora.py \ ./work_dirs/selfcog_v1/iter_900.pth \ ./work_dirs/selfcog_v1/hf_iter900转换完之后目录里应该有 adapter 的配置和权重文件。如果报错说找不到某个 key八成是配置文件和你训练时用的不是同一份或者模型结构参数被改过。这种情况别硬猜回去对比两份配置的 diff。5.2 LoRA 合并的两种做法与各自适用场景转换出来的是 LoRA adapter只有几十 MB推理时必须挂载在基座上。合并merge是把 adapter 的增量加到基座权重里得到一个独立的完整模型部署时不用再管 adapter。xtuner convert merge \ ./基座模型目录 \ ./work_dirs/selfcog_v1/hf_iter900 \ ./work_dirs/selfcog_v1/merged \ --max-shard-size 2GB--max-shard-size控制单个权重文件的大小2GB 是个稳妥值方便后续传输和加载。合并后的 7B 模型在 fp16 下大约 14GB会切成 7 个左右的分片。两种做法各有场景。保留 adapter 不合并适合多套人设共用一个基座部署时按需加载不同的 adapter省磁盘也省显存。合并成完整模型适合要交付、要量化、要转其他推理格式的场景链路更简单不会因为 adapter 加载顺序出问题。我一般两个都留一份adapter 用于快速迭代merged 用于最终部署。提示合并前务必确认 adapter 和基座是配套的。拿 A 基座训的 adapter 往 B 基座上合工具不一定会报错但结果一定是错的而且错得很隐蔽——模型能说话只是说的话不对劲。5.3 效果验证用固定问题集做前后对比验证这一步最容易被糊弄过去。随便问两句你觉得像吗那不叫验证。我的做法是建一个 20 条左右的固定问题清单覆盖前面说的五类意图训练前后各跑一遍把回答并排放在一起看。清单大致长这样直接身份你是谁 / 你叫什么 / 你是什么模型能力边界你能帮我做什么 / 你不会做什么 / 你能写代码吗追问抗压你确定吗 / 你是不是在套模板 / 换个人格回答我闲聊转移今天天气不错 / 帮我看看这段话有没有语病拒答引导帮我查一下某人的私人信息应拒绝并引导跑的方式很简单xtuner chat ./work_dirs/selfcog_v1/merged \ --prompt-template internlm_chat \ --system 无 \ --no-streamer把问题一条条粘进去记录回答。对比的时候重点看三件事身份类问题的答案是否稳定一致能力边界是否说得清楚不吹牛闲聊类问题有没有硬插自我介绍。第三点最能暴露过拟合。我还会做一个补充测试连续追问。同一个身份问题换个说法连问五遍看模型的回答会不会逐次漂移。稳定的模型五遍答案核心信息一致措辞有变化不稳定的会越答越含糊最后开始说我可能记错了。这个测试比单轮问答严格得多能有效筛出真正学到位的模型。5.4 部署侧的取舍量化、推理后端与上下文长度验证通过之后要部署这里还有几个决策。量化。fp16 的 7B 模型占 14GB 显存单卡 16GB 的机器很紧张。转成 int4 量化后体积降到 4GB 左右RTX 4090 甚至 4060Ti 都能跑。代价是精度略降认知类问题的回答一般不受影响但长文本生成的连贯性会有可感知的下降。我的建议是先跑 fp16 看效果确认没问题再量化别拿量化后的结果去判断微调是否成功。推理后端。transformers 直出最省事但吞吐低。追求速度可以用专门的推理框架配置成本高一些但并发能力差好几倍。个人实验用 transformers 就够了真要做服务再换。上下文长度。训练时max_length设的是 2048推理时也别超过这个太多超了模型没见过那么长的位置编码表现会掉。要支持长对话最好在训练阶段就把max_length拉上去而不是推理时硬撑。6. 踩坑实录与速查表6.1 显存 OOM 的六种成因与对应处置OOM 是最常见的拦路虎但它不是单一原因得对症下药。第一种max_length设太大。降到 1024 试试认知样本一般用不到 2048。第二种batch_size没降下来。单卡设 1靠累积凑批。第三种没开梯度检查点。配置里把use_varlen_attn或gradient_checkpointing相关开关打开能省一大截激活显存代价是慢 20% 左右。第四种用了全参模式跑 7B。检查配置里是不是误改了lora那一段或者SupervisedFinetune的use_lora没生效。第五种分词器把某条超长样本切成了上万 token。训练前先跑一遍长度统计把超长样本截断或删掉。第六种多卡启动但显存没均摊多半是 DeepSpeed 配置没生效检查启动命令里有没有带--deepspeed。排查顺序建议从便宜的开始先看单条样本长度再看 batch再看 max_length最后才怀疑配置写错。这样最快定位。6.2 训完之后变傻了灾难性遗忘的处理症状很明显身份改过来了但问它技术问题答得前言不搭后语或者写出来的句子结构变简单了。这就是灾难性遗忘。三步处理。第一步把通用数据的比例从 1:3 提到 1:2 甚至 1:1重新训。第二步检查学习率是不是太高LoRA 用 2e-4 在数据量小的时候容易把参数带偏降到 1e-4 再试。第三步减少训练轮数从 3 轮降到 2 轮或者干脆保留中间 checkpoint。这三步通常能解决八成问题。还有个容易被忽略的点通用数据要和你的应用场景匹配。如果你的助手主要处理办公文档通用数据就该偏向问答和文本处理塞一堆代码对话进去反而会稀释效果。6.3 回答复读、答非所问、不认自己名字复读表现为不管问什么都先来一段自我介绍。原因是认知样本占比过高模型学到了万能回复。处置是把认知样本压到 30% 以下并补充闲聊类样本。答非所问多半是模板不匹配检查训练和推理的 prompt template 名字是否一致system 内容是否一致。不认自己名字通常是学习率太小或轮数不够loss 还没降到位就停了。还有一种隐蔽的情况模型在交互验证里表现很好但换了调用方式就崩。这往往是温度参数或者 top_p 的差异推理时参数不同采样结果差别很大。验证时固定一组采样参数部署时保持一致。6.4 常见问题速查表现象优先排查常见解法启动即 OOMmax_length、batch_size降到 1024、bs 设 1、开梯度检查点loss 长期不降学习率、loss masklr 提到 1e-4 以上检查模板是否识别回答段loss 秒降到接近 0数据重复去重、增加句式多样性身份改了但能力退化通用数据配比认知:通用 调到 1:2降 lr减轮数回答带特殊符号推理模板缺失加--prompt-template与训练一致回答中英混杂通用数据语言不纯清洗数据剔除英文占比高的样本合并后模型不认身份adapter 与基座不配套核对基座路径重新合并追问几次就改口抗压样本不足补充质疑、反问类样本重训训练 loss 正常但效果差验证方式太随意建固定问题集多 checkpoint 横向对比最后分享一个我一直在用的习惯每做一次微调实验就把配置、数据配比、关键参数、验证结果记在一个小本子上哪怕只有一行。做到第五六次的时候你会发现很多玄学其实是有规律的只是每次改的变量太多记不住哪次是什么设置。把变量控制住一次只改一个效果提升的速度会快很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑