刚入坑微调,用LLaMA-Factory对Qwen2-7B做LoRA微调,数据集是自己整理的200条对话(json格式),学习率设了2e-4,跑了3个epoch。结果测试的时候,模型输出的内容全是重复的乱码符号,比如“�ll�ll��”这种。我看了一下loss曲线是下降的,但生成结果完全不能用。想问下这可能是哪里出问题了?是数据集格式不对,还是超参数设置有问题?或者LoRA的rank值设太高/太低?查了几个教程都没找到类似案例,求大佬指点一下方向。
LLaMA-Factory微调后模型输出全是乱码,有人遇到过吗?
全部回复
共 10 条这情况我遇到过两次,大概率是tokenizer和模型微调时的词表没对齐。检查下你数据集里是否混入了特殊字符或未转义的unicode,另外试试把learning rate降到1e-4以下,LoRA rank设8-16跑一两个epoch看看loss是否真的在下降。如果还是乱码,可以贴一段你的json样本出来,我帮你看看格式问题。
哈哈这个我熟,之前调Qwen系也踩过类似的坑。乱码输出大概率是tokenizer和模型权重没对齐,你检查下LLaMA-Factory加载模型时是不是忘了设trust_remote_code=True?或者数据集里的special tokens处理有问题。另外200条数据跑3个epochloss下降可能是过拟合到噪声上了,试试把学习率降到1e-4以下、rank值调低到8-16,先拿几条数据跑个1epoch验证下tokenizer输出正不正常。
我最近也遇到类似的问题,不过我是用别的库试的。我猜可能是tokenizer和模型没对齐?比如你保存模型的时候tokeniz
er配置没跟着更新,或者数据集里有些特殊字符被编码成别的东西了。要不试试加载预训练权重后先跑一条测试,排除是微调过程的问题?
这问题我太熟了,早期做LoRA的时候被类似的问题折磨过整整一周,连觉都睡不好。先别急着怀疑人生,你遇到的这个“乱码符号”现象,其实在微调新手里非常典型,尤其是刚接触LLaMA-Factory或者类似框架的时候。我先给你吃颗定心丸:Loss下降是正常的,因为模型在强行拟合你的数据分布,哪怕数据是错的,它也能把Loss压下去,但生成结果崩了说明它学的根本不是语言规律,而是某种“噪音模式”。下面我从几个最可能的方向帮你拆解,每一个都是我亲手踩过坑的。
第一,最可能的原因:数据集格式问题,尤其是JSON里字段名不对。LLaMA-Factory默认期望的对话格式是类似ShareGPT的格式,也就是每条数据要有“conversations”这个键,里面是一个列表,每个元素是包含“from”和“value”的字典,“from”可以是“human”或“gpt”。很多人刚接触时容易写成“input”和“output”,或者“instruction”和“response”,甚至直接用“text”字段。这种格式不对,框架在读取时会把内容当成纯文本直接拼到prompt里,导致模型看到的实际输入是“{“conversations”: [{“from”: “human”, “value”: “你好”}]}”这种JSON字符串本身,而不是你想要的对话内容。模型被迫去学习输出这个JSON的乱码变体,结果自然就是满屏符号。你可以先检查一下你数据集的字段名,用Python加载一条数据,打印出来看看key的名字是不是被框架识别到了。如果你不确定,最简单的办法是去LLaMA-Factory的data目录下找个示例数据集,比如“identity.json”,完全照搬它的格式,哪怕内容只改一句,先跑通一个epoch,确认不是格式问题。
第二,一个更隐蔽的陷阱:tokenizer和模型不匹配。Qwen2-7B本身用的是Qwen2Tokenizer,如果你在加载模型时没有显式指定tokenizer路径,或者用了错误的tokenizer名称,比如写成“Qwen/Qwen2-7B-Instruct”但实际加载的是base模型,或者反过来,那tokenizer的special tokens(比如<|im_start|>, <|im_end|>)会完全错位。特别要注意的是,Qwen2的对话模板是需要在输入前后加特殊标记的,如果LLaMA-Factory的模板配置没跟上,或者你在自定义数据集里手动加了这些标记但格式不对,模型在生成时就会把特殊token当成普通字符输出,变成乱码。一个典型的症状就是生成结果里出现大量重复的“<|im_end|>”或者“�”这种替换字符。解决方法是在训练前打印一条预处理后的输入样本,看看tokenizer.decode出来的内容是不是合理的对话文本。如果不是,检查一下模板配置,LLaMA-Factory的dataset_info.json里有个“template”字段,确保它设置成了“qwen”或者对应的模型名。
第三,超参数设置的问题,但你可能想不到是哪个参数在作怪。学习率2e-4对LoRA来说其实偏高,虽然Qwen2-7B这种规模的模型在LoRA上通常用1e-4到5e-5,但2e-4不一定会直接导致乱码,它更多是导致收敛不稳定。真正能引发乱码的往往是“cutoff_len”或者“max_seq_length”设置得太长或太短。比如你数据集里有些对话很长,但你设的max_seq_length只有512,那超过的部分会被直接截断,而且截断位置恰好在一个token的中间字节上,导致解码时出现非法UTF-8序列,输出就是频繁的“�”。还有可能是你开了“neftune_noise_alpha”这个参数,它会在embedding上加噪声,虽然能提升泛化,但对小数据集噪声过大时,模型会把噪声当成信号去学,生成完全随机的内容。建议你先关掉所有data augmentation相关的参数,用最朴素的配置跑一次。
第四,一个很少有人提但非常关键的点:LoRA的target_modules选择。很多人直接默认全量target,或者只选了“q_proj”“v_proj”,但Qwen2的模型结构里,注意力层命名可能和LLaMA不完全一样。比如Qwen2里有些层叫“q_proj”“k_proj”“v_proj”“o_proj”,而LLaMA-Factory的默认配置可能只适配了LLaMA系列。如果你没手动指定,框架可能会用默认的LLaMA模块名去匹配,结果一个都没匹配上,LoRA根本没生效,你实际在微调的是全量参数但学习率极低,相当于白训。你可以检查训练日志里有没有类似“modules not found”的警告,或者用一个极小的测试集,训练后对比base模型和微调模型在同一个输入上的输出是否有变化,如果完全一样,那就是LoRA没挂上。正确做法是在配置里显式写明“lora_target: all”,或者指定为“q_proj,k_proj,v_proj,o_proj,gate_proj,up_proj,down_proj”这些Qwen2实际存在的层。
第五,别忘了检查你的数据集本身是否干净。200条对话听起来不多,但如果里面混了空字符串、全角符号、或者编码问题(比如UTF-8和GBK混用),那模型在训练时就会学到这些异常模式。我之前遇到过一个案例,数据是从Excel导出的CSV再转JSON的,中间某些单元格里残留了不可见字符,比如“\u200b”零宽空格,模型居然学会了在每次输出后都插入一堆零宽空格,看起来就像乱码。建议你用hexdump或者Python的repr()函数检查每条数据的前100个字符,看看有没有异常的Unicode字符。另外,如果你的对话是中文,但模型tokenizer的词汇表里对某些生僻字覆盖率低,那也会被映射成UNK,导致输出出现“�”。Qwen2的中文覆盖已经很好,但如果你用了大量专业术语或表情符号,还是有可能触发。
第六,一个容易被忽视的流程问题:你是在什么环境下测试的?是在训练脚本里直接调用了model.generate(),还是用了LLaMA-Factory自带的inference脚本?如果是前者,要注意你是否正确设置了tokenizer的padding_side和truncation策略,很多人在训练时设了padding_side='left',但推理时忘了改回来,导致输入序列被错误填充,生成时从填充位置开始解码,直接输出一堆空格和乱码。另外,检查一下你是否在推理时设置了do_sample=True和temperature=0.1,如果temperature设得太高或者top_p太大,模型可能会从高熵分布中采样,输出随机字符。在修复问题前,建议先设成greedy decoding(do_sample=False)排除随机性干扰。
第七,如果你已经排除了以上所有问题,那可能是LLaMA-Factory框架本身的版本bug。这个框架迭代很快,有些commit会引入奇怪的问题。比如我去年遇到过一个情况,某个版本的LLaMA-Factory在加载自定义数据集时,会自动在每条对话前加一个“system”角色的空消息,导致模型输出的第一个token永远是system的回复,也就是空字符串,然后模型就会陷入重复生成空内容的死循环。你可以尝试更新到最新版本,或者回退到一个稳定版本(比如v0.6.0之后的某个tag)。另外,检查一下你的CUDA版本和PyTorch版本是否兼容,有些环境问题会导致tensor计算时出现NaN,而NaN被解码后也会变成乱码。你可以在训练过程中打印几个中间层的梯度范数,如果出现inf或NaN,那就是数值稳定性问题,需要降低学习率或者增加warmup steps。
最后,给你一个快速验证的“最小复现”思路:不要用你自己的数据,先用LLaMA-Factory自带的“alpaca_zh_demo”或者“identity”数据集,用完全相同的超参数跑一遍。如果自带数据集也出乱码,那一定是你的环境或配置有问题;如果自带数据集正常,那就100%是你数据集的问题。然后你从200条数据里只取前5条,用最简单的格式(比如只有一轮对话),看能不能正常训练和生成。如果能,再逐步增加数据量和对话轮次,定位出具体是哪条数据或者哪个模式导致的崩溃。这个过程听起来繁琐,但往往能在半小时内找到问题根因。
记住,乱码不是模型变笨了,而是它学到了你不希望它学的东西。你现在的任务不是调参,而是先让模型学会“说人话”。从数据格式和tokenizer入手,99%的问题都能解决。加油,等你搞定这个坎,后面微调的坑还多着呢。
这问题我当初也踩过类似的坑,说几个排查方向吧。
首先,乱码“�ll�ll��”这种大概率是tokenizer和模型不匹配,或者数据集里混进了特殊字符。你用的Qwen2-7B,它的tokenizer对中文字符比较敏感,建议先检查下json文件里对话内容有没有隐藏的不可见字符(比如从word或pdf直接复制过来的换行符、零宽空格),用notepad++或hex工具看一眼。另外,确认下你加载模型时tokenizer是不是用的Qwen2原版的,别手滑用了别的。
其次,学习率2e-4对LoRA来说稍微有点高,尤其你只用了200条数据,这个规模下模型很容易过拟合到训练集的噪声上,导致输出崩溃。建议降到1e-4甚至5e-5试试,同时把epoch降到2,因为200条数据3个epoch可能已经学“死”了。LoRA rank值一般8-16就够,过高反而容易丢失泛化性,可以试试rank=8,alpha=16。
还有一个容易忽略的点:数据集格式。LLaMA-Factory对json的字段名有严格要求(比如instruction、input、output),你确认下键名是不是完全匹配?尤其是对话类任务,有时需要system字段,但如果你只用了“对话”而不是“指令-回答”结构,模型可能会把乱码当正常内容学进去。建议先拿一条数据单独跑个predict,看看tokenizer解码后的输入输出是什么样。
最后,loss下降不代表一切正常,可能在拟合乱码本身。建议把训练日志里的tokenizer解码结果也打印出来,看看中间步骤有没有开始出现重复字符。如果这些排查完还不行,试试换一个公开数据集(比如alpaca格式)跑一遍,排除数据本身的问题。
我之前也遇到过类似情况,排查下来发现是tokenizer和模型权重没对齐,比如微调时用的tokenizer配置跟原版不一致。还有个小细节,你检查下数据集里有没有奇怪的BOM头或者不可见字符?另外LoRA rank用8或者16一般不会出这种乱码问题,倒是学习率2e-4对7B模型可能偏大了,试试1e-4甚至5e-5呢。
看到你这个现象,我第一反应是“经典复现”——不是你的问题,而是几乎所有刚入坑微调的人都会撞上的墙。我搞大模型微调落地三年多了,从早期的LLaMA、ChatGLM到现在的Qwen和Mistral,踩过的坑比你列出来的多得多。你这现象我至少见过五六种不同的成因,而且每一种都能导致“loss下降但输出乱码”,所以别慌,我们一个一个拆。
先说结论:最可能的原因是数据和tokenizer之间的错位,尤其是“tokenizer不知道如何解码你生成的logits”导致退化成乱码。但为了不遗漏其他可能性,我按优先级从高到低给你排一个排查清单,每个点我都会给出实际案例和操作建议。
第一,检查你的tokenizer是否与模型完全匹配。你用Qwen2-7B,它的tokenizer是基于tiktoken的字节对编码实现,和LLaMA系列不同。如果你在加载模型时错用了Qwen1的tokenizer,或者更常见的是,你在配置文件中遗漏了trust_remote_code=True,那么tokenizer可能无法正确识别模型词汇表中的特殊token(比如<|im_start|>, <|im_end|>之类的对话分隔符)。这时候模型生成的token id虽然是在词汇表内,但tokenizer解码时会映射到未定义的Unicode区域,输出就成了“�ll�ll��”这种。解决方法很简单:确保加载tokenizer时指定use_fast=True,并且从模型官方仓库直接加载,不要用本地缓存的老版本。我有个同事曾经因为conda环境里缓存了旧版qwen tokenizer,导致生成结果全是
第二,数据集格式问题比你想的更致命。你用的是json格式的200条对话,但关键不在于格式,而在于你是否正确构造了模型的输入格式。Qwen2的对话模板要求每条消息必须带有角色标签,比如{"role": "user", "content": "..."}和{"role": "assistant", "content": "..."},并且在训练时你需要用apply_chat_template方法将对话转换成模型能理解的文本。如果你直接丢给模型原始的json字典,或者自己手动拼接但漏掉了特殊的结束标记,模型可能会把用户输入和助手输出混淆,导致在生成阶段产生死循环——反复输出同一个token,然后被tokenizer解码成重复的乱码。我建议你打印一条处理后的训练样本,看看它长什么样。正常应该是类似这样的文本:<|im_start|>user\n你好<|im_end|>\n<|im_start|>assistant\n你好呀,有什么可以帮你的?<|im_end|>。如果你发现角色标记丢失或者换行符不对,那就是数据预处理部分出了问题。另外,200条数据太少了,Qwen2-7B这种7B参数的模型,LoRA虽然参数量小,但200条对话连一个主题都覆盖不全,很容易过拟合到几个特定模式上,生成时反复输出训练集中出现过的片段。
第三,学习率2e-4对于7B模型做LoRA来说偏高了。LoRA的秩通常只有8到64,你学的是低秩矩阵的增量,不是全量参数。2e-4是很多全量微调时使用的初始学习率,对于LoRA来说,尤其是数据量小的时候,这个学习率可能导致权重更新过于剧烈,模型在训练后期震荡,最终收敛到一个局部极值点——这个极值点虽然loss低,但生成的token分布完全偏离了语言模型原本的分布。我建议你降到5e-5甚至1e-5,先用一个epoch看看输出是否正常。如果输出还是乱码,但loss下降变慢了,那就说明学习率确实是问题。我在微调Qwen2-72B时,LoRA学习率设到1e-4都会出现输出重复,降到3e-5才稳定。
第四,LoRA的rank值一般不会直接导致乱码,但会影响模型能否记住足够的信息。rank设得太低(比如4或8)且数据量又小,模型可能根本没有足够的表达能力去拟合你的对话模式,导致生成时退化成随机输出。设得太高(比如128或256)又容易过拟合。对于200条数据,我建议用rank=16或32,alpha设成16或32,不要太大。还有一个容易被忽略的点:你是否正确配置了target_modules?Qwen2的Transformer结构中,LoRA通常只加到注意力层的q_proj和v_proj上,如果你加到了所有线性层(比如lm_head或embed_tokens),那可能会破坏模型的输出分布。你可以用peft的get_peft_model打印一下可训练参数的数量,如果超过总参数的1%,那就说明你加多了。
第五,检查你的生成参数。很多人在微调后测试时直接用了训练时的配置,比如temperature=0.1或者top_p=0.9,但微调后的模型对生成参数非常敏感。如果你在训练时使用了标签平滑或者dropout,而推理时没有关闭,也可能导致输出异常。我建议你先用最保守的生成参数测试:temperature=0, top_p=1, repetition_penalty=1.0, max_new_tokens=128。如果这样输出正常,再逐步调整参数。另外,检查一下你的padding side。Qwen2默认是左padding,但如果你在推理时用了右padding,可能导致模型看到一堆填充token然后输出乱码。这个坑我至少见过三次,最后发现是data collator里没设置padding_side='left'。
第六,如果你用的是bnb量化加载(比如4bit),那问题可能出在量化参数上。量化后的模型对梯度噪声更敏感,LoRA微调时如果学习率不当,量化误差会被放大。建议你先用fp16或bf16加载,排除量化干扰。如果fp16下正常,那就是量化配置的问题。另外,检查一下你的计算设备是否支持你选的精度,比如在V100上试图用bf16会导致数值不稳定。
第七,loss曲线下降但生成乱码,还有可能是你的评估指标选错了。交叉熵loss下降只代表模型学会了预测下一个token的概率分布,但如果你的数据集里存在大量重复模式(比如每条对话都以同样的问候语结尾),模型可能学会了只输出高频token,导致生成时陷入局部循环。你需要检查一下训练集里是否有这样的模式,比如超过20%的样本都以“好的”结尾。如果有,可以考虑数据增强或者添加重复惩罚。
最后,给你一个快速验证的方法:在训练完成后,不要直接测试整个对话,而是先做一步“回归测试”。从训练集中取一条样本,输入给模型,看它能不能正确复现训练集里的输出。如果连训练集都复现不了,那肯定是训练过程出了问题;如果能复现但对新输入乱码,那就是过拟合或者数据泄露。你可以写个简单的脚本,比较模型输出和训练集目标之间的token id分布,看看是否大多数token id都在预期范围内。如果模型输出大量出现在词汇表中未定义的token id(比如大于vocab_size的id),那很可能是vocab_size配置错误或者tokenizer与模型不匹配。
说了这么多,其实核心就一句话:乱码几乎总是tokenizer或数据预处理的问题,先检查这两个,再调超参。我当年第一次微调LLaMA-7B时,花了整整一周才发现是因为没有正确设置pad_token_id,导致模型把pad token当成正常文本生成了。你这才刚开始,踩过的坑以后都会变成你的经验。有问题随时继续问,我可以把具体代码贴出来。
我遇到过类似情况,后来发现是tokenizer没对齐——用LLaMA-Factory默认的tokenizer加载Qwen2可能会有编码问题,试试在微调前手动指定分词器路径。另外200条数据跑3个epoch可能有点少,LoRA rank值一般设8-16比较稳,太高容易过拟合导致乱码。你检查下json里response字段是不是纯文本,有没有混进特殊字符?
我也遇到过类似情况,后来发现是tokenizer和模型没对齐,加载预训练权重时vocab大小不一致就容易出乱码。你可以检查下数据集的tokenization过程,看看是不是特殊token没处理好。另外2e-4对于7B模型来说稍微高了点,试试降到1e-4或者5e-5,同时把epoch减少到1-2轮,防止过拟合到那200条数据上。rank值一般设8-16就行,太低容易欠拟合,但太高对小数据集反而可能不稳定。
我遇到过类似情况,当时是分词器没对齐导致的,检查一下微调时用的tokenizer是否和base模型一致,特别是Qwen2的chat模板要单独处理。另外你的200条数据量确实偏小,学习率2e-4对LoRA来说稍微高了点,建议降到1e-4试试,rank值保持8或16一般不会出乱码。loss下降但生成乱码,很多时候是数据集里混了特殊字符或者格式问题,可以跑一条空白输入看看输出是否正常。