在做毕设的多模态大模型微调时,我遇到了一个很奇怪的问题:模型明明已经给出了正确回答,却会在正确回答后紧接着输出一大段乱码。
经探索发现:这一问题的根源是结尾符(EOS Token)的输出问题。大语言模型的推理以自回归概率输出为基础,判断回答结束的方法也是识别到输出结果中的结尾符,而Qwen base模型的微调过程中正会出现与之相关的问题,导致了本文所述现象的发生。本文将探讨这一问题的原因和解决方案。
问题描述
在使用Qwen2.5-3B做岩相识别任务的多模态LoRA微调时,3个epoch后loss收敛到可接受范围,进一步测试时发现生成结果长这样:
结合数据集标注,该样本更可能为「斜长岩」。<U+FFFD>
ניוזל
始化
这组数据累计多少个样本?
useRalative
anton
累计数量为10 个样本。
...(无限续写)第一行是模型生成的正确回答,但随后紧接着模型又输出了大量的希伯来文、乱码字节、莫名其妙的英文单词。这导致了两方面问题:
- 实际模型输出会包含这些乱码内容,不满足我们问答模型的需求
- 计算测试集评估指标时,这些垃圾内容把分数拉得很低
问题排查
大语言模型通过自回归按概率输出每个词,直到遇到结尾符(又称为停止token)才停止输出。在Qwen2.5 的Chat格式里,每一轮对话结束用的是 <|im_end|> 这个特殊 token(id=151645),而不是llama等常用的 <|endoftext|>(id=151643)结尾符。
注意到:我们的模型输出中完全没有包含这两个结尾符中的任意一个。进一步排查问题,最终在GitHub的一个issue:[[QwenLM/Qwen3 #1064]]1)里找到了答案,是一位开发者通过打印权重发现的:
# 打印 Qwen2.5-Base 的 lm_head 权重
print(model.lm_head.weight[151643]) # <|endoftext|>:正常,有独特的值
print(model.lm_head.weight[151644]) # <|im_start|>:所有值约为 ±1.17e-37
print(model.lm_head.weight[151645]) # <|im_end|>:和 151644 完全一样
print(model.lm_head.weight[151646]) # 其他视觉特殊 token:也一样
print(model.lm_head.weight[151647]) # ...
# 151644 ~ 151664 全部共享相同的权重值!即:Qwen2.5-Base 在预训练时,从来没有训练过 <|im_end|> 这类特殊 token。它们的 lm_head(输出层)权重,全部被初始化成了同一个随机值,因此没有在训练中被更新。而我们用 LoRA微调时,LoRA只更新Transformer的注意力层,不更新lm_head。这意味着:
- LoRA微调后,确实通过注意力层,让模型时希望停止输出时,结尾符的输出概率提高了
- 但上述lm.head中相邻其他几个token的权重和停止符完全一样,所以它们的输出概率也在微调时被同步提高了,且幅度完全相同
- 模型推理采样时,argmax在这20多个输出概率相同的token里随机选一个,导致了输出结尾符前,其他字符也因概率被输出为回答的一部分。
正因如此,<|vision_pad|>、<|object_ref_start|> 这些视觉token解码出来是 \uFFFD(Unicode 替换字符)和各种乱七八糟的字节,导致了这样的异常输出。
解决方案
在LoraConfig里加一行,让 lm_head 也参与训练:
lora_cfg = LoraConfig(
r=lora_r,
lora_alpha=lora_alpha,
lora_dropout=lora_dropout,
target_modules=target_modules,
modules_to_save=["lm_head"], # ← 加这一行
bias="none",
task_type="CAUSAL_LM",
)modules_to_save的作用是在LoRA微调时,同时保存并更新这些模块的完整权重(不是低秩近似,而是原始的全参数)。这样lm_head就会在训练中被独立更新,不再和其他特殊token共享权重了。
事实上,这是Qwen官方文档对Base模型微调的推荐做法:"LoRA (emb) refers to training with embedding and output layers as trainable parameters, required when fine-tuning base models with new tokens."
代价是checkpoint会多保存一份完整的 lm_head权重(约600MB),训练时间基本不变。
Discussion
这个问题在Qwen2.5系列的Base模型微调里是一个已知的共性坑。我们的项目从Base→CPT→SFT的模型训练路线踩了这个坑。若使用instruct模型,则其在在RLHF阶段已经训练过<|im_end|>,lm_head 权重是独特的,不存在这个问题。
为防止机器人刷评,请输入验证码~
验证码可能加载较慢,请耐心等待 | 点击图片可刷新验证码