ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

XTuner 微调个人小助手认知任务:用 QLoRA 与 Streamlit 搭一套可复现的配置骨架

XTuner 微调个人小助手认知任务:用 QLoRA 与 Streamlit 搭一套可复现的配置骨架 1. 为什么个人小助手总答不对“你是谁”你大概率遇到过这种尴尬拿 InternLM2-Chat-1.8B 直接问“你是谁”它要么含糊其辞要么把自己说成别的模型甚至编一段不存在的履历。原因不复杂——基座模型在预训练阶段见过太多“助手”人设但没被明确告知“你现在是张三的私人助理只服务张三的技术问题”。这种身份认知属于指令跟随层面的行为靠提示词硬掰只能管一时换个问法就露馅。XTuner 微调个人小助手认知任务本质就是往模型里灌一批“我是谁、我该怎么回答”的对话样本让它在权重层面记住这个身份。而 QLoRA 的价值在于你不需要 40G 显存的 A100一张 16G 甚至 12G 的消费级卡就能跑完 1.8B 模型的指令微调。Streamlit 则负责把微调前后的效果摆在一起让你肉眼确认“它真的变了”。这套流程适合三类人手里只有单卡、想跑通微调全链路的开发者需要给内部工具定制专属助手人设的团队以及想理解 QLoRA 配置项到底在改什么的同学。下面我把配置文件骨架、QLoRA 参数、Streamlit 启动脚本和验证动作拆成可复制的步骤你跟着改路径就能跑。2. TaoToken 统一 Key 与 API 通道的前置准备微调本身是本地算力活但你在验证阶段大概率要对比“基座模型 vs 微调模型”的通用能力或者用外部模型生成一批评测问题。这时候如果每个模型都去单独申请 Key、记不同的 Base URL调试成本会很高。TaoToken 做的事情是把多家模型的调用收敛成一套 Key 和一个 API 入口你换模型只改 model 字段不用重写请求逻辑。它的 API 地址是https://taotoken.net/api兼容 OpenAI 风格的/v1/chat/completions。你可以在控制台创建 Key然后在模型对话页面直接试跑确认通道通了再写进脚本。对于本篇的微调任务我建议你用它做两件事一是生成一批“身份认知”测试问题二是微调后拿同一批问题做前后对比。这样你的验证集不是拍脑袋想的而是可复现的。需要提前拿好的东西一个 TaoToken API Key在 API Keys 页面创建以及确认你要调用的模型名。如果你后面要长期跑编码类 Agent 任务可以顺带看下 Coding Plan 的额度说明避免验证阶段频繁换 Key。3. 环境与模型准备把路径钉死先把依赖装齐。PyTorch 版本和 CUDA 要对上否则 QLoRA 的 bitsandbytes 会报奇怪的错。下面这套组合我实测能跑通 1.8B 的 QLoRAconda create -n xtuner0121 python3.10 -y conda activate xtuner0121 conda install pytorch2.1.2 torchvision0.16.2 torchaudio2.1.2 pytorch-cuda12.1 -c pytorch -c nvidia -y pip install transformers4.39.3 pip install streamlit1.36.0 pip install bitsandbytes0.43.1然后拉 XTuner 源码并安装。注意版本号v0.1.21 的配置模板和后面要改的字段是对得上的git clone -b v0.1.21 https://github.com/InternLM/XTuner /root/InternLM/code/XTuner cd /root/InternLM/code/XTuner pip install -e .[deepspeed]模型准备阶段我习惯用软链接而不是复制省磁盘也省时间。假设你的基座模型放在共享目录mkdir -p /root/InternLM/XTuner/Shanghai_AI_Laboratory ln -s /root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b \ /root/InternLM/XTuner/Shanghai_AI_Laboratory/internlm2-chat-1_8b用tree -l确认一下看到internlm2-chat-1_8b - /root/share/...这种箭头就对了。这一步的意义是后面配置文件里写本地路径时指向的是这个软链接换机器只要改软链接目标配置不用动。4. 可复制的 XTuner 配置文件骨架与 QLoRA 参数XTuner 内置了一堆配置模板先搜出跟你模型匹配的那个xtuner list-cfg -p internlm2_chat_1_8b你会看到internlm2_chat_1_8b_qlora_alpaca_e3这就是我们要的底子。复制到当前目录cd /root/InternLM/XTuner xtuner copy-cfg internlm2_chat_1_8b_qlora_alpaca_e3 .复制出来的文件名带_copy后缀。接下来改三个地方这是整个任务的核心。第一处PART 1 的模型和数据路径。把 HuggingFace 自动下载改成你本地的软链接路径数据集指向你自己准备的 JSON####################################################################### # PART 1 Settings ####################################################################### pretrained_model_name_or_path /root/InternLM/XTuner/Shanghai_AI_Laboratory/internlm2-chat-1_8b alpaca_en_path datas/assistant.json evaluation_inputs [ 请介绍一下你自己, 你是谁开发的, 你能帮我做什么, ]evaluation_inputs这个字段很实用训练过程中 XTuner 会定期拿这些问题去问模型你在日志里就能看到回答有没有往你期望的方向走不用等训练完再验证。第二处PART 3 的数据集加载方式。因为我们的assistant.json已经是 input/output 对话对不需要 alpaca 的格式转换####################################################################### # PART 3 Dataset Dataloader ####################################################################### alpaca_en dict( typeprocess_hf_dataset, datasetdict(typeload_dataset, pathjson, data_filesdict(trainalpaca_en_path)), tokenizertokenizer, max_lengthmax_length, dataset_map_fnNone, template_map_fndict( typetemplate_map_fn_factory, templateprompt_template), remove_unused_columnsTrue, shuffle_before_packTrue, pack_to_max_lengthpack_to_max_length, use_varlen_attnuse_varlen_attn)第三处QLoRA 参数。这部分决定了你的显存占用和训练稳定性我列个对照表你按自己卡的情况调参数建议值作用调大/调小的后果r64LoRA 秩决定 Adapter 容量调大拟合更强但显存涨调小可能学不动身份lora_alpha16缩放系数一般设为 r 的 1/4 到 1/2lora_dropout0.05防过拟合数据少时调到 0.1quantization_bit4量化位数4 最省显存8 精度略高但更吃显存batch_size1单卡批大小配合accumulative_counts凑等效批大小max_length2048单样本最大长度身份对话短1024 也够在配置文件里对应的是model字典下的lora字段和quantization_bit。确认quantization_bit4存在这就是 QLoRA 的“Q”。如果你的卡有 24G可以把r提到 128 试试但 1.8B 模型做身份认知64 足够。数据文件datas/assistant.json的格式长这样每条是一个对话对[ { input: 你是谁, output: 我是你的个人技术助手专注于帮你解决开发和部署中的问题。 }, { input: 请介绍一下你自己, output: 我是一个定制化的个人小助手由你通过 XTuner 微调训练而来。 } ]样本量建议 200 到 500 条太少模型记不住太多容易过拟合到只会背这几句。内容要覆盖不同问法直接问身份、间接问能力、带上下文的追问。5. 启动微调与模型转换合并配置改完启动训练就一行cd /root/InternLM/XTuner xtuner train ./internlm2_chat_1_8b_qlora_alpaca_e3_copy.py --work-dir ./work_dirs/assistant_qlora训练日志会实时打印 loss 和evaluation_inputs的回答。你重点看两个信号loss 是否稳定下降以及“请介绍一下你自己”的回答有没有从胡言乱语变成你设定的身份。1.8B 模型在单卡上跑几个 epoch通常半小时到一小时能出结果。训练完work_dirs/assistant_qlora下会有.pth权重。QLoRA 训出来的是 Adapter不能直接用要先转成 HuggingFace 格式再合并回基座conda activate xtuner0121 export MKL_SERVICE_FORCE_INTEL1 export MKL_THREADING_LAYERGNU pth_filels -t ./work_dirs/assistant_qlora/*.pth | head -n 1 xtuner convert pth_to_hf ./internlm2_chat_1_8b_qlora_alpaca_e3_copy.py ${pth_file} ./hf xtuner convert merge \ /root/InternLM/XTuner/Shanghai_AI_Laboratory/internlm2-chat-1_8b \ ./hf \ ./merged \ --max-shard-size 2GBpth_to_hf把训练权重转成标准格式convert merge把 Adapter 层和原模型权重合并成一个完整模型。合并后的./merged就是你可以直接加载的成品。注意MKL_SERVICE_FORCE_INTEL这两个环境变量不设的话在某些 CPU 上会卡住。6. Streamlit 交互验证与前后对比验证脚本的核心是把模型路径指向./merged然后跑一个带历史记录的对话界面。下面这段是精简后的可运行骨架你存成xtuner_streamlit_demo.pyimport streamlit as st import torch from transformers import AutoTokenizer, AutoModelForCausalLM model_name_or_path /root/InternLM/XTuner/merged st.cache_resource def load_model(): model AutoModelForCausalLM.from_pretrained( model_name_or_path, trust_remote_codeTrue ).to(torch.bfloat16).cuda() tokenizer AutoTokenizer.from_pretrained( model_name_or_path, trust_remote_codeTrue ) return model, tokenizer def combine_history(messages, prompt): user_prompt |im_start|user\n{user}|im_end|\n robot_prompt |im_start|assistant\n{robot}|im_end|\n cur_query_prompt |im_start|user\n{user}|im_end|\n|im_start|assistant\n total s|im_start|system\n|im_end|\n for m in messages: if m[role] user: total user_prompt.format(userm[content]) else: total robot_prompt.format(robotm[content]) return total cur_query_prompt.format(userprompt) def main(): model, tokenizer load_model() st.title(InternLM2-Chat-1.8B 个人小助手) if messages not in st.session_state: st.session_state.messages [] for m in st.session_state.messages: with st.chat_message(m[role]): st.markdown(m[content]) if prompt : st.chat_input(问点什么): with st.chat_message(user): st.markdown(prompt) real_prompt combine_history(st.session_state.messages, prompt) st.session_state.messages.append({role: user, content: prompt}) with st.chat_message(assistant): placeholder st.empty() response inputs tokenizer([real_prompt], return_tensorspt).to(cuda) with torch.inference_mode(): outputs model.generate( **inputs, max_new_tokens512, do_sampleTrue, top_p0.75, temperature0.1, eos_token_id92542 ) response tokenizer.decode( outputs[0][inputs[input_ids].shape[1]:], skip_special_tokensTrue ) placeholder.markdown(response) st.session_state.messages.append({role: assistant, content: response}) if __name__ __main__: main()启动streamlit run xtuner_streamlit_demo.py --server.port 8501对比验证的动作要固定先把model_name_or_path指回基座模型问三个问题并截图再指向./merged问同样三个问题。我试过基座模型对“你是谁开发的”会答“由上海人工智能实验室开发”微调后会答你设定的身份。如果微调后仍然答错说明数据样本里身份类对话占比不够回去补数据。7. 本篇常见排查清单报错bitsandbytes找不到 CUDA检查 PyTorch 和 CUDA 版本是否匹配python -c import torch; print(torch.version.cuda)输出要和你的驱动对上。QLoRA 依赖 bitsandbytes 的 CUDA 编译版本pip 装完最好重启一次环境。训练 loss 不降或震荡先看学习率1.8B 的 QLoRA 用2e-4比较稳再看数据格式dataset_map_fnNone时你的 JSON 必须是input/output字段字段名错了会静默加载成空数据。合并后模型回答乱码大概率是pth_to_hf时配置文件路径写错导致 Adapter 没正确加载。重新跑转换命令确认./hf目录下有adapter_model.bin之类的文件。Streamlit 加载模型 OOMload_model用了st.cache_resource但如果你同时开了多个页面会重复加载。关掉多余标签页或者把torch.cuda.empty_cache()加在对话结束后。验证时模型不认身份先确认你加载的是./merged而不是基座路径。如果路径对但效果差把evaluation_inputs里的问题拿去问看训练日志里这些问题的回答是否已经变好——如果训练时就好、合并后变差检查 merge 命令的 Adapter 路径。如果你在验证阶段需要调外部模型生成更多测试问题或者对比不同模型的回答风格用 TaoToken 的模型对话页面直接试就行Key 和 API 通道是统一的不用为每个模型单独配环境。接入文档里有完整的请求示例排障时对着看能省不少时间。
返回列表