最近在微调 Qwen VL 模型,使用 peft 库的 lora 进行微调。为了更高的推理效率,就把 lora 合并到了基底模型。但合并过后的模型输出效果非常差。
不应该啊,没合并前还好好的。从原理上 lora 合并与不合并的输出是等价的。难道是精度问题?
思考
基于 float16 或 bfloat16 的半精度训练现在已经非常成熟。像是 Qwen 这样的大模型基本是使用 bfloat16 精度存储的模型权重。
要说 lora 合并带来的损失,我能想到的就只有精度上的损失了。接下来用个小一点的模型实验一番。
先说结论:基底模型权重是 float32 才能忽略 lora 合并的损失。
实验
使用以下代码进行实验:
import torchfrom peft import LoraConfig, get_peft_modelfrom transformers import AutoModelForCausalLM, AutoTokenizertorch.manual_seed(0)model = AutoModelForCausalLM.from_pretrained( "facebook/opt-125m", torch_dtype=torch.float32,)input = torch.tensor([[1, 2, 3, 4, 5]])# lora modelconfig = LoraConfig( r=8, init_lora_weights=False,)model_new = get_peft_model( model, config, adapter_name="adapter1",)# adapter1 outputmodel_new.set_adapter("adapter1")output_adapter1 = model_new(input).logitsmodel_new.merge_and_unload(safe_merge=True, adapter_names=["adapter1"])output_base = model_new.get_base_model()(input).logitsdiff = output_adapter1 - output_baseprint(torch.sum(torch.abs(diff)))
代码用 lora 输出与模型合并输出进行对比,输出结果为 0.2223
。鉴于输出维度为 [1, 5, 50272],这个误差几乎能够忽略不计。
现在将基底模型设置为 bfloat16,再进行测试。
···model = AutoModelForCausalLM.from_pretrained( "facebook/opt-125m", torch_dtype=torch.bfloat16,)···
结果猛然上升到 2416.
,误差上升了万倍。
如果是 float16 呢?毕竟 bfloat16 是用动态换精度的格式,换用 float16 应该会好些。
···model = AutoModelForCausalLM.from_pretrained( "facebook/opt-125m", torch_dtype=torch.float16,)···
其结果为 302.7500
。仍然很高。
如果,对于 bfloat16 的基底模型,先转换为 float32 再合并 lora,结果会不会有所改善?
···model = AutoModelForCausalLM.from_pretrained( "facebook/opt-125m", torch_dtype=torch.bfloat16,)···model = model.float()model_new.merge_and_unload(safe_merge=True, adapter_names=["adapter1"])model = model.bfloat16()···
输出结果 2416.
。没有改善。
碎碎念
网上只能搜到用 QLoRA 微调后合并权重会出现精度损失的信息,提出的解决方法是把基底模型上采样到 float32 再进行合并。QLoRA 毕竟是把基底模型量化到了 Int4,就是没想到 bfloat16 也会出现问题。
半精度训练或推理能减少显存占用、充分利用上 tensor core 加速,获得种种好处的同时模型仍然能够保持较高准确度,不使用半精度不太现实。为了更好的效果,要么全量微调,要么只能不合并 lora 去推理了。