model.to(bf16)/half() and Trainer bf16_full_eval cast RoPE inv_freq buffers, unlike from_pretrained(dtype=…): large angle error at long co

当你在加载模型之后再用 model.to(torch.bfloat16) / .half() / .bfloat16() 或 Trainer(bf16_full_eval=True) 做全量评估时,RoPE 的 inv_freq 等频率 buffer 会被一起降精度,导致长上下文下角度误差迅速放大;

快速结论:当你在加载模型之后再用 model.to(torch.bfloat16) / .half() / .bfloat16() 或 Trainer(bf16_full_eval=True) 做全量评估时,RoPE 的 inv_freq 等频率 buffer 会被一起降精度,导致长上下文下角度误差迅速放大;优先排查加载后是否发生了 dtype 转换。

适用环境:transformers main @ 4692303,也复现于 5.18.0;torch 2.11;CPU 环境(Issue 中所有测量均在 CPU 上完成)。

最快修复方案:暂无确认的一步修复方案。Issue 中给出的 PreTrainedModel._apply 原型改动属于提议方案,报告者自测对 Llama 类模型和语音模型有效,但尚未被合入,需要以实际版本验证。

注意事项:不要把它理解成“bf16 评估不可用”。from_pretrained(dtype=torch.bfloat16) 路径会保留 inv_freq 为 fp32,误差仅来自返回的 cos/sin 量化;问题只出现在加载后再 .to() 的路径。对语音模型(如 Wav2Vec2Conformer)还会出现位置值塌缩,影响更早出现。

问题场景

用户在 Transformers 中加载模型后,通过 model.to(torch.bfloat16)、model.bfloat16()、model.half() 等方式手动转换精度;或在使用 Trainer 并设置 TrainingArguments(bf16_full_eval=True) 时,Trainer 内部会对模型执行 model.to(dtype=torch.bfloat16)。此时如果模型使用 RoPE(旋转位置编码),其频率 buffer 会被一并转换,造成长上下文位置角度与 fp32 参考值出现明显偏差。

受影响的模型不限于 Llama 系。Issue 中额外点名了 Wav2Vec2Conformer、SeamlessM4T、Wav2Vec2Bert、Clvp,以及构建实数角度输入的 MusicFlamingo。这些模型在 .to(torch.bfloat16) 后,旋转位置会在 10 秒音频附近就开始塌缩。

报错原文

model.to(bf16)/half() and Trainer bf16_full_eval cast RoPE inv_freq buffers, unlike from_pretrained(dtype=...): large angle error at long context

torch.float32 torch.bfloat16
from_pretrained(dtype=bf16)  angle error vs fp32: mean 0.0006 rad, 0.0% of angles off by > 0.5 rad
model.to(bf16)               angle error vs fp32: mean 0.6733 rad, 36.1% of angles off by > 0.5 rad

10s: 121/499 positions collapsed, max angle error 1.00 rad, 11.5% of angles off by > 0.1 rad
30s: 927/1499 positions collapsed, max angle error 3.00 rad, 28.2% of angles off by > 0.1 rad

原因分析

核心原因是 model.to(dtype) 会把模块中所有浮点 buffer 一起转换,包括 RoPE 使用的 inv_freq、original_inv_freq 以及各个 layer type 对应的 *_inv_freq。而 from_pretrained(dtype=torch.bfloat16) 在加载阶段只把模型参数转换为 bf16,这些频率 buffer 仍保持 fp32,因此两条常见路径得到的 RoPE 角度并不一致。

RoPE 的 forward 里虽然存在 .float() 与 maybe_autocast(enabled=False) 保护,但它们只能避免 autocast 期间的精度损失。一旦 buffer 本身已经被舍入到 bf16,后续再调用 .float() 也无法恢复已经丢失的精度。误差会随位置索引增大而累积,在 32767 位置附近,bf16 量化带来的累计漂移可达约 5.85 rad。

对语音模型而言,问题更严重:Wav2Vec2Conformer、SeamlessM4T、Wav2Vec2Bert、Clvp 会以 buffer 的 dtype 构建旋转位置(time_stamps = torch.arange(sequence_length).type_as(self.inv_freq))。当 inv_freq 被降为 bf16 时,原本不同的时间帧位置会舍入到同一个值,导致位置塌缩。这也是同一个 .to() 精度问题在语音模型上的放大表现。

Issue 中还引用了 timm 的同类处理方式(huggingface/pytorch-image-models#2797):_RotaryEmbeddingBase._apply 在频率 buffer 被降精度时会将其重建为 fp32,理由是频率会先乘以可能很大的位置数,再进入 sin/cos,低精度量化会与位置放大效应叠加。

环境排查

  • 确认 Transformers 版本:Issue 在 main @ 4692303 复现,也在 5.18.0 复现。
  • 确认 PyTorch 版本:Issue 使用 torch 2.11。
  • 确认运行设备:Issue 中所有测量均在 CPU 上完成。
  • 确认模型加载与精度转换顺序:是先 from_pretrained(..., dtype=torch.bfloat16),还是先加载再用 model.to(torch.bfloat16) / .half() / .bfloat16() 转换。
  • 如果使用 Trainer,确认是否设置了 bf16_full_eval=True,因为该配置会触发模型级别的 dtype 转换。
  • 若为语音模型,确认是否属于 Wav2Vec2Conformer、SeamlessM4T、Wav2Vec2Bert、Clvp 等使用旋转位置编码且以 buffer dtype 构建位置值的架构。
  • 检查目标模型的 inv_freq 或 *_inv_freq buffer 的 dtype,判断是否已被降为 bf16/fp16。

解决步骤

  1. 先判断问题路径:检查模型加载后是否还执行了 model.to(...)、.bfloat16()、.half(),或 Trainer 是否启用了 bf16_full_eval=True。
  2. 如果只是需要 bf16 推理或评估,优先尝试改用 from_pretrained(..., dtype=torch.bfloat16),让模型在加载阶段完成 dtype 转换,避免对 inv_freq 这类频率 buffer 做后置降精度。
  3. 对于必须使用 Trainer(bf16_full_eval=True) 的场景,可优先尝试在评估前记录目标模型的 inv_freq(或 *_inv_freq)buffer 的原始 fp32 值,并在 dtype 转换后将其恢复为 fp32;此为 Issue 中 _apply 原型方案的思路,尚未合入主线。
  4. 如果存在自定义模型或自定义 RoPE 模块,可优先尝试参照 timm 的做法:在 _apply 中检测频率 buffer 被降精度时,将其重建为 fp32,而不是保留 bf16/fp16。
  5. 上述修改都需要覆盖 to、half、bfloat16、float、cuda 等所有经过 _apply 的路径;Meta tensor(to_empty)不应被重新填充,.double() 路径也应保持原行为。
  6. 验证时同时跑两条路径:from_pretrained(dtype=bf16) 与手动 .to(bf16),比较两者的 inv_freq.dtype 与长上下文角度误差。若两者的角度误差与 fp32 参考一致,说明修复生效。

验证方法

用 Issue 中的最小复现脚本确认:loaded.model.rotary_emb.inv_freq.dtype 应为 torch.float32,cast.model.rotary_emb.inv_freq.dtype 修复前为 torch.bfloat16。在 max_position_embeddings=32768 下,from_pretrained(dtype=bf16) 的角度误差均值应在 0.0006 rad 量级、超过 0.5 rad 的比例为 0.0%;model.to(bf16) 修复前误差均值约 0.6733 rad、超过 0.5 rad 的比例约 36.1%。对语音模型,修复后 10s 与 30s 应均为 0 collapsed、0.00 rad,而不是修复前的 121/499 与 927/1499 位置塌缩。

参考来源

huggingface/transformers #49288

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 27353

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注