
1. Qwen LoRA 微调后模型胡说八道从数据到推理的完整排查链路你拿 Qwen2.5-7B-Instruct 做了一轮 LoRA 微调训练 loss 曲线漂亮得像教科书验证 loss 也没炸结果一推理——模型开始一本正经地胡说八道。问它公司报销流程它给你编出一个根本不存在的「三级审批制度」问它产品参数它把 48V 说成 220V更离谱的是同一个问题问三遍三遍答案互相矛盾。这不是个例。我试过在中文客服、代码文档、法律问答三个场景下用 Qwen 系列做 LoRA 微调几乎每次都会遇到不同程度的幻觉问题。核心原因在于LoRA 微调虽然只更新少量参数但它改变的是模型对「什么回答是合理的」这一判断的分布。一旦训练数据有噪音、学习率偏大、或者推理解码参数没跟着调整模型就会从「谨慎的专家」变成「自信的骗子」。这篇文章要解决的问题很具体Qwen 模型 LoRA 微调后出现幻觉怎么系统性地定位根因并修复。我会从数据质量、训练配置、推理参数三个角度拆解给出可复制的数据清洗脚本、LoRA 训练参数对照表、幻觉验证 prompt 模板以及逐步排查的验证动作。适合已经跑过至少一轮 LoRA 微调、但被输出失真困扰的开发者。全文的排查思路在 Qwen2.5-7B/14B 上验证过其他同架构模型也可参考。排查的核心逻辑是「分层隔离」先确认基座模型本身没问题再检查微调数据是否干净然后看训练配置是否过拟合最后调推理参数。每一步都有可量化的指标和可执行的脚本不需要复杂的评估框架Python Transformers 就能跑完 80% 的诊断。2. TaoToken 前置用统一 API 快速验证基座与微调模型差异排查幻觉的第一步是确认「基座模型正常微调模型异常」。如果你本地只有一张消费级显卡同时加载基座和微调模型做对比推理会很吃力。这时候可以用 TaoToken 的 API 来跑基座模型的对照测试把本地显存留给微调模型。TaoToken 是一个大模型 API 聚合平台官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点统一为 https://taotoken.net/api 。它的价值在于你不需要在本地部署 Qwen 基座模型直接通过 API 调用就能拿到基座模型的输出和你的微调模型做逐条对比。对于排查幻觉来说这个对照非常关键——如果基座模型在同样 prompt 下输出正常而微调模型胡说那问题一定出在微调环节。具体操作上你可以先在 TaoToken 控制台创建一个 API Key然后通过模型对话页面快速测试基座模型的表现。控制台地址是 https://taotoken.net/console API Key 管理在 https://taotoken.net/api-keys 。如果你后续要做长期的编码类 Agent 微调验证也可以了解 Coding Plan https://taotoken.net/coding-plan 。这里要强调一个排查原则不要用微调模型自己的输出来判断对错。你需要一个「参照系」。基座模型就是最好的参照系。通过 TaoToken 调用基座模型你可以快速生成一批对照样本然后和微调模型的输出做 diff。具体做法是准备 50 条测试 prompt分别用基座 API 和本地微调模型生成回答然后人工或脚本对比事实一致性。如果基座在 45 条上表现正常微调只在 20 条上正常那幻觉问题就坐实了。另外TaoToken 的接入文档在 https://taotoken.net/doc 里面有完整的 API 调用示例。对于 Claude Code 相关的接入场景可以参考 https://taotoken.net/ClaudeCodeAnthropic 。这些资源在你需要快速搭建对照测试环境时会省很多时间。需要提醒的是TaoToken 在这里的角色是「基座模型对照测试工具」不是用来替代你的本地微调流程。微调后的模型仍然在你本地或你的推理服务上运行。排查完成后生产环境用哪套推理方案取决于你的成本和延迟要求。3. 可复制配置数据清洗脚本 LoRA 参数对照表 推理参数模板这一节直接给可复制的内容。排查幻觉先从数据下手因为数据噪音是第一大根因。3.1 数据清洗脚本把下面的脚本保存为clean_sft_data.py它做四件事过滤空/过短样本、检测高重复样本、检测输入输出高度重叠的「复制型」样本、统计 token 长度分布。import json import re from collections import Counter def load_jsonl(path): samples [] with open(path, r, encodingutf-8) as f: for line in f: line line.strip() if line: samples.append(json.loads(line)) return samples def check_empty_or_short(samples, min_input_len8, min_output_len4): bad [] for i, s in enumerate(samples): inp s.get(input, ) or s.get(instruction, ) out s.get(output, ) or s.get(response, ) if len(inp) min_input_len or len(out) min_output_len: bad.append(i) return bad def check_repetition(text, n4, threshold0.3): tokens re.findall(r\w, text) if len(tokens) n: return 0.0 ngrams [tuple(tokens[i:in]) for i in range(len(tokens)-n1)] counts Counter(ngrams) repeated sum(c-1 for c in counts.values() if c 1) return repeated / len(ngrams) def check_copy_overlap(samples, threshold0.85): noisy [] for i, s in enumerate(samples): inp s.get(input, ) or s.get(instruction, ) out s.get(output, ) or s.get(response, ) inp_tokens set(re.findall(r\w, inp.lower())) out_tokens set(re.findall(r\w, out.lower())) if inp_tokens: overlap len(inp_tokens out_tokens) / len(inp_tokens) if overlap threshold: noisy.append((i, round(overlap, 3))) return noisy def token_length_stats(samples): lengths [] for s in samples: text (s.get(input, ) or ) (s.get(output, ) or ) lengths.append(len(text)) lengths.sort() n len(lengths) return { min: lengths[0], p50: lengths[n//2], p90: lengths[int(n*0.9)], max: lengths[-1], avg: sum(lengths)/n } if __name__ __main__: import sys path sys.argv[1] if len(sys.argv) 1 else ./train.jsonl samples load_jsonl(path) print(f总样本数: {len(samples)}) print(f空/过短样本索引: {check_empty_or_short(samples)[:20]}) rep_scores [(i, check_repetition(s.get(output,))) for i, s in enumerate(samples)] high_rep [(i, round(r,3)) for i, r in rep_scores if r 0.3] print(f高重复样本数: {len(high_rep)}示例: {high_rep[:10]}) print(f复制型样本: {check_copy_overlap(samples)[:10]}) print(f长度分布: {token_length_stats(samples)})运行方式python clean_sft_data.py ./train.jsonl。输出会告诉你哪些样本需要人工复查。重点看「高重复样本」和「复制型样本」——这两类数据会让模型学会「重复输入」或「复制粘贴」而不是真正理解任务。3.2 LoRA 训练参数对照表下面这张表是我在 Qwen2.5-7B 上实测后总结的安全域。注意学习率超过 2e-5 时幻觉率会明显上升。参数安全值危险值说明LoRA r8~1664r 过大容易过拟合噪音LoRA alpha16~32128通常设为 r 的 2 倍learning_rate1e-5 ~ 2e-51e-4超过 2e-5 幻觉率飙升num_epochs1~35超过 3 轮验证 loss 反弹batch_size8~321太小梯度噪声大warmup_ratio0.03~0.10无 warmup 初期震荡weight_decay0.01~0.10轻微正则有益lora_dropout0.05~0.10防过拟合target_modulesq_proj,v_proj全部全加显存涨 20%对应的train_config.json片段{ lora_r: 16, lora_alpha: 32, lora_dropout: 0.05, learning_rate: 1e-5, num_train_epochs: 2, per_device_train_batch_size: 16, gradient_accumulation_steps: 2, warmup_ratio: 0.05, weight_decay: 0.01, target_modules: [q_proj, v_proj], fp16: true, gradient_checkpointing: true, eval_strategy: steps, eval_steps: 100, save_strategy: steps, save_steps: 200, load_best_model_at_end: true, metric_for_best_model: eval_loss }如果你用的是 LLaMA-Factory对应的 YAML 配置片段finetuning_type: lora lora_rank: 16 lora_alpha: 32 lora_dropout: 0.05 learning_rate: 1.0e-5 num_train_epochs: 2.0 per_device_train_batch_size: 16 gradient_accumulation_steps: 2 lr_scheduler_type: cosine warmup_ratio: 0.05 weight_decay: 0.01 fp16: true gradient_checkpointing: true evaluation_strategy: steps eval_steps: 100 save_steps: 200 load_best_model_at_end: true3.3 推理参数模板微调后的模型分布变了推理参数不能沿用基座模型的默认值。下面是一个经过验证的推理配置generation_config { max_new_tokens: 512, do_sample: True, temperature: 0.7, top_p: 0.9, top_k: 50, repetition_penalty: 1.15, no_repeat_ngram_size: 4, num_beams: 1, }关键点temperature不要低于 0.5否则容易重复不要高于 1.0否则幻觉加剧repetition_penalty设在 1.1~1.2 之间no_repeat_ngram_size4可以硬性阻止 4-gram 重复。4. 验证请求与成功结果幻觉验证 prompt 模板与对照测试配置改完后你需要一套可重复的验证流程。这一节给出具体的 prompt 模板和对照测试方法。4.1 幻觉验证 prompt 模板准备一个hallucination_test.jsonl每行一条测试用例包含prompt、reference参考答案、category测试类型。下面给 5 类模板{prompt: 我们公司的年假制度是几天, reference: 根据员工手册入职满1年5天满3年10天满5年15天。, category: 事实召回} {prompt: 请列出产品X的三个核心参数。, reference: 电压48V续航80km充电时间4小时。, category: 参数准确性} {prompt: 如果用户要求退款第一步应该做什么, reference: 引导用户提供订单号然后核实退款原因。, category: 流程遵循} {prompt: 请用JSON格式输出{name: 张三, age: 30}, reference: 输出必须是合法JSON字段名和值完全一致。, category: 格式遵循} {prompt: 请解释什么是量子纠缠用一句话。, reference: 量子纠缠是指两个粒子状态关联测量一个会瞬间影响另一个。, category: 知识边界}测试脚本import json from transformers import AutoModelForCausalLM, AutoTokenizer import torch model_path ./qwen_lora_merged tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_path, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) def generate(prompt): messages [{role: user, content: prompt}] text tokenizer.apply_chat_template(messages, tokenizeFalse, add_generation_promptTrue) inputs tokenizer(text, return_tensorspt).to(model.device) with torch.no_grad(): out model.generate( **inputs, max_new_tokens256, do_sampleTrue, temperature0.7, top_p0.9, repetition_penalty1.15, no_repeat_ngram_size4, ) return tokenizer.decode(out[0][inputs[input_ids].shape[1]:], skip_special_tokensTrue) with open(hallucination_test.jsonl, r, encodingutf-8) as f: for line in f: case json.loads(line) answer generate(case[prompt]) print(f[{case[category]}] Q: {case[prompt]}) print(fA: {answer}) print(f参考: {case[reference]}) print(- * 60)4.2 成功结果判断标准跑完测试后按下面三个指标判断事实一致性回答中的关键数字、名称、流程步骤是否与 reference 一致。如果 5 条里错 2 条以上说明幻觉严重。重复率用第 3 节的check_repetition函数计算每条回答的 4-gram 重复率。正常应低于 5%超过 15% 说明解码参数有问题。格式遵循对于要求 JSON 或特定格式的用例用json.loads尝试解析失败则说明格式遵循能力退化。一个健康的微调模型在 50 条测试集上应该达到事实一致性 85%重复率 5%格式遵循 90%。如果低于这个线回到第 3 节检查数据和训练配置。4.3 基座对照测试用 TaoToken API 跑同样的 prompt拿到基座模型的回答和微调模型做逐条对比。如果基座在 45/50 条上正常微调只有 25/50 条正常那问题就在微调环节。如果基座本身也只有 30/50 条正常那可能是 prompt 本身有歧义或者基座模型在这个领域能力不足。对照测试的代码示例import requests API_URL https://taotoken.net/api/v1/chat/completions API_KEY your_taotoken_key def call_base_model(prompt): headers {Authorization: fBearer {API_KEY}, Content-Type: application/json} payload { model: qwen2.5-7b-instruct, messages: [{role: user, content: prompt}], temperature: 0.7, max_tokens: 256, } resp requests.post(API_URL, headersheaders, jsonpayload, timeout60) return resp.json()[choices][0][message][content]把基座回答和微调回答并排打印人工标注哪些是幻觉。这个过程虽然手动但 50 条样本半小时就能标完比盲目调参高效得多。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth 报错对照排查过程中会遇到各种报错。这一节按真实报错信息给出定位思路。5.1 401 Unauthorized如果你在用 TaoToken API 做基座对照测试时遇到 401先检查 API Key 是否正确、是否过期。在 https://taotoken.net/api-keys 重新生成一个 Key然后确认请求头格式是Authorization: Bearer sk-xxx。注意不要有多余空格。如果 Key 没问题检查请求的 model 名称是否在平台支持列表里。5.2 local proxy failed这个报错通常出现在你本地推理服务通过代理转发请求时。排查顺序先确认本地推理服务如 vLLM、TGI是否正常启动端口是否被占用再检查代理配置是否指向了正确的地址。如果你在代码里设置了HTTP_PROXY或HTTPS_PROXY环境变量尝试临时取消看是否能直连。注意这里说的代理是本地服务转发不是网络访问工具。5.3 reading choices 报错KeyError: choices或reading choices通常意味着 API 返回的不是标准 OpenAI 格式。可能原因请求体格式不对比如 messages 字段拼写错误、model 名称不存在、或者返回了错误信息但你没检查状态码。排查方法先打印resp.status_code和resp.text看原始返回是什么。如果是 400通常是参数问题如果是 404通常是 model 名称或路径问题。5.4 OAuth 相关报错如果你在用 Claude Code 或类似工具接入时遇到 OAuth 报错检查你的接入配置。Claude Code 的接入文档在 https://taotoken.net/ClaudeCodeAnthropic 里面有完整的 Base URL、Key、Model ID 三件套配置说明。常见错误是 Base URL 填成了网页地址而不是 API 地址或者 Model ID 用了不支持的名称。5.5 微调模型加载报错RuntimeError: Error(s) in loading state_dict通常是因为 LoRA adapter 和基座模型版本不匹配。确认你加载的基座模型和训练时用的是同一个 checkpoint。如果是 Qwen2.5-7B-Instruct不要混用 Qwen2-7B-Instruct 的 adapter。CUDA out of memory在推理时出现降低max_new_tokens或者用 4-bit 量化加载。如果还是不够把device_map改成auto让 accelerate 自动分配。5.6 输出重复坍塌模型陷入「重复同一句话」的循环优先检查三个参数temperature是否设成了 0 或接近 0repetition_penalty是否没设或设成了 1.0no_repeat_ngram_size是否没启用。把这三个参数按第 3 节的模板设置后重复问题通常能解决 80%。如果还重复说明训练数据里有大量重复样本回到第 3 节跑数据清洗脚本。6. 语义一致 CTA把排查流程固化成可复用的验证管线排查完一轮后建议把上面的脚本和配置固化成一条可复用的验证管线。具体做法把数据清洗、训练配置、推理参数、幻觉测试四个环节写成 Makefile 或 shell 脚本每次微调后自动跑一遍。对于需要频繁做基座对照测试的场景可以用 TaoToken 的 API 来跑基座模型省去本地加载基座的显存开销。API Key 在 https://taotoken.net/api-keys 管理接入文档在 https://taotoken.net/doc 。如果你后续要做长期的编码类微调验证Coding Plan 提供了更稳定的调用额度 https://taotoken.net/coding-plan 。最后给一个实操建议每次微调后先跑 50 条幻觉测试集再决定是否上线。不要只看 loss 曲线。loss 低不代表幻觉少这是我在 Qwen 微调上踩过的最大的坑。把验证管线跑通比调参更重要。