资讯动态

从LoRA到OFT:Qwen2.5-VL多模态微调在昇腾与CUDA的实战

发布时间:2026/10/21 3:54:50 来源:尧图企业网站定制
1. 项目背景为什么要折腾多模态微调最近这半年多模态大模型的应用需求暴涨尤其是像Qwen2.5-VL这种既能看图又能理解文本的模型几乎成了很多业务场景的标配底座。我所在的团队也接到一个具体需求需要让模型在某一类特定风格的图片上做细粒度的图文理解比如工业图纸上的标注识别、特定产品图上的属性抽取这类场景用通用底座模型直接推理效果总差那么一截所以微调是绕不开的路径。一开始我们理所当然想用LoRA毕竟生态成熟、资料多、踩坑的人也多遇到问题容易搜到答案。但在实际推进过程中LoRA在昇腾910B上暴露出一些让人头疼的兼容性问题反倒是后来切换到的OFTOrthogonal Finetuning方案在昇腾上跑得意外顺畅最终效果也更好。这篇文章就把这段从LoRA到OFT的迁移过程、两个硬件平台的适配差异、以及过程中积累的踩坑经验完整记录下来给准备做Qwen2.5-VL微调、尤其是需要跨GPU平台CUDA和CANN折腾的朋友一个参考。阅读这篇内容之前默认你已经了解什么是LoRA、什么是Qwen2.5-VL、也大概知道transformers和peft库的基本用法。如果你还没接触过这些建议先跑通一遍推理流程再来看微调否则部分术语和参数解释会显得比较跳跃。文章会覆盖从环境配置到训练调优再到问题排查的完整链路适合正在准备做多模态微调、或者已经在微调路上被平台兼容性折磨的工程师阅读。2. 方案选型LoRA、OFT与多模态模型的适配逻辑2.1 LoRA为什么成为首选以及它到底改了什么LoRA的核心逻辑是通过低秩分解来模拟权重更新。原始权重矩阵W是d×d的规模LoRA把它拆成一个d×r的矩阵A和一个r×d的矩阵B训练时只更新A和B原始权重完全冻结。当r远小于d时可训练参数量就从d²缩减到2×d×r通常只有原来的0.1%到1%。这个思路在大语言模型上被验证得非常好原因是语言模型的权重矩阵本身存在着较低的内在秩用低秩扰动去适配新任务完全够用。但在多模态模型上情况稍微复杂一些。Qwen2.5-VL这类模型除了文本的attention层和FFN层还有视觉编码器vision encoder和跨模态连接模块这些模块的权重特性各不相同。视觉编码器提取的是图像的底层视觉特征它对任务适配的敏感度和文本层不一样如果直接套用语言模型的LoRA策略往往会出现视觉特征适配不足的问题。我刚开始做的时候就是按惯性把LoRA加在所有的query、key、value和output投影层上结果在昇腾上跑了两轮loss降得倒是快但验证集上的指标提升非常有限。后来仔细分析才发现视觉编码器部分我根本没加LoRA模块模型微调的实际上是文本侧对视觉特征的“解读方式”而不是视觉特征本身。这解释了为什么在涉及细粒度视觉理解的任务上表现不佳。这是LoRA方案第一个需要注意的地方多模态模型微调必须考虑视觉和文本两侧的适配范围。2.2 OFT是什么为什么它能解决LoRA的局限性OFT的全称是Orthogonal Finetuning它和LoRA的底层思路不同。LoRA是在原始权重上加一个低秩增量相当于允许权重向量在任意方向上发生偏移。而OFT做的事情是学习一组正交变换矩阵保持特征向量的范数和相互之间的夹角不变的前提下旋转特征空间。专业术语可能不好直接理解我用人话解释一下LoRA把特征空间的形状改了OFT只是把特征空间转了个角度。从数学性质上讲OFT对特征分布的扰动更小特别适合视觉编码器这类需要保持特征语义稳定性的模块。这是因为正交变换不会改变特征之间的相对位置模型在预训练阶段学到的知识不会被破坏得太狠对于需要高精度语义对齐的多模态任务来说这种“温和”的特性反而更有价值。在Qwen2.5-VL上OFT的一个经典用法是只对视觉编码器的attention层做正交微调文本部分保持冻结。这样一来模型的文本理解能力完全不动只调整视觉特征的空间排布以适配目标任务。在我实际测试的业务场景里OFT在视觉侧的效果普遍比LoRA高2到3个点尤其是在细粒度属性识别任务上优势更明显。但OFT也有代价计算量比LoRA大。正交约束的更新过程需要额外做矩阵正交化前向和反向的计算量都会增加。在4090上跑的时候体感不明显但在昇腾910B上由于CANN和CUDA的算子实现差异OFT的准备阶段多花了将近两倍的工程适配时间。这部分后面详细讲。2.3 两个平台的选型决策团队手头的算力资源比较特殊一台GPU服务器用的是RTX 4090另一台是国产加速卡的昇腾910B。这两台机器都要充分利用所以从项目一开始就要考虑代码能否在两套环境上无缝切换。4090的优势非常明显CUDA生态成熟PyTorch的GPU支持开箱即用无论是transformers还是peft库都能直接跑通。昇腾910B则麻烦得多虽然算力指标不差但它的底层计算架构是达·芬奇架构通过CANNCompute Architecture for Neural Networks开发套件来提供PyTorch适配层。PyTorch生态中的很多算子昇腾上不一定有高效的实现甚至某些操作需要手动改写为昇腾支持的算子才能运行。当时选型时的策略是先让LoRA在4090上验证业务效果确认微调路线的可行性再迁移到昇腾上做生产环境的规模化训练。事实证明这个策略是正确的因为LoRA在CUDA上跑通只需要一天而昇腾环境上的环境适配就花掉了四天时间期间还踩了不少算子兼容性的坑。3. 硬件环境与依赖配置昇腾910B和4090的差异化适配3.1 4090环境搭建4090的环境相对无脑找个干净的Ubuntu 20.04系统装好NVIDIA驱动和CUDA 12.x然后创建conda环境装PyTorch 2.x和对应的tensorflow、transformers、peft、accelerate库就能直接开始跑。唯一要注意的是4090的显存是24GBQwen2.5-VL的微调如果带上视觉编码器的梯度batch size稍微大一点就容易爆显存这一点后面细说。4090环境我推荐直接用官方预编译的PyTorch wheel包省去源码编译的时间。CUDA版本最好锁定12.1或12.4因为新版的PyTorch对CUDA版本有一定要求装错了版本轻则跑不了GPU算子重则整个环境都容易出问题。确认一切都装好之后可以用一行简单的代码验证GPU可用性import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))输出都是True和GeForce RTX 4090就说明环境没问题。还有一个容易被忽略的细节是要确认cuDNN的版本和PyTorch编译时使用的版本兼容否则某些卷积类的算子在推理时会抛出奇怪的错误信息。3.2 昇腾910B环境搭建昇腾环境的搭建就麻烦多了。首先要确认设备型号昇腾910B有不同规格算力差异很大需要先检查NPU信息。然后安装昇腾的固件和驱动版本号需要和硬件匹配否则后边创建context就会报错。接下来是CANN工具包这个是最核心的部分它包含算子库、编译器、运行环境以及PyTorch适配插件。昇腾上跑PyTorch的核心是torch_npu这个插件它通过PyTorch的插件机制将算子转发到CANN实现上使大部分PyTorch代码可以无缝运行在昇腾NPU上。安装方式是用pip安装torch-npu包但需要注意版本必须和PyTorch严格对应比如PyTorch 2.1.0对应torch_npu某个特定版本版本不匹配会导致import时报错。装完之后用以下命令验证环境python -c import torch, torch_npu; print(torch.npu.is_available()) python -c import torch; print(torch.npu.get_device_name(0))如果输出True和类似Ascend910B的字符串说明NPU环境可以正常使用。但这里要提醒一句能import不代表所有算子都能跑。Qwen2.5-VL的模型中包含很多复杂的attention实现某些算子比如某些flash attention的实现在昇腾上可能不存在直接对应的高效算子需要回退到慢速的普通实现甚至某些情况下需要手动改代码。这个是昇腾适配中最耗时的地方后面会有专门的小节讲。3.3 代码层面的双平台兼容技巧为了让同一份代码既能在4090上跑又能在昇腾上跑我在项目里加了一个设备检测模块思路很简单先尝试import torch_npu如果成功就使用AscendNPU设备否则回退到CUDA。def get_device(): import torch try: import torch_npu if torch.npu.is_available(): return npu except ImportError: pass if torch.cuda.is_available(): return cuda return cpu有了这个设备判断之后所有涉及device的操作都需要改为动态获取比如model.to(device)、inputs.to(device)等。还有一个细节是数据加载器的pin_memory参数在昇腾上并不一定被支持如果写上容易导致打数据时崩溃所以要根据平台动态设置。卡在昇腾上的一个常见错误就是pin_memoryTrue导致Dataloader在取数据时抛错改成pin_memoryFalse后问题消失。还有一个比较隐蔽的问题是随机数种子。昇腾NPU的随机数生成机制和CUDA不完全一样同样的seed在不同平台上生成的随机序列可能不同。为了做跨平台的对比实验最好在训练脚本里同时固定Python、NumPy以及框架层面的随机种子并且把数据加载的shuffle逻辑也控制住否则两个平台的实验起点不同效果对比就失真了。我在做效果对比时因为这个问题吃过暗亏重跑了几轮实验才意识到。4. LoRA微调实战参数设置、训练流程与4090上的效果验证4.1 数据集准备这次微调任务用的数据是某工业场景下的一批产品图片每张图片对应一条文本描述描述中包含产品编号、颜色、瑕疵类型三个属性。数据总量大约是2万条训练集1.6万、验证集4000。图片分辨率统一resize到448×448对应Qwen2.5-VL的视觉塔输入尺寸。数据格式上参考了Qwen2.5-VL官方微调的格式用JSON组织每条数据就是一张图片加一段对话文本。需要注意的细节是图片路径的处理方式不要在数据集中使用绝对路径否则迁移到昇腾机器时全部要重写。我用的是相对路径配合项目根目录拼接这样同一份数据在4090和昇腾上都能直接读取。这个习惯在后续跨机器实验时省了不少事。还有一个同样重要的点是数据预处理的像素值归一化问题。Qwen2.5-VL的视觉编码器要求输入图像经过特定标准的归一化处理使用ImageNet的mean和std。如果数据集中存在不同来源的图片有的图片已经做过归一化有的没有混合在一起训练会导致loss震荡得非常厉害。建议用transform的预处理器一步到位不要在训练代码里手动归一化否则容易在数据加载时出错。from transformers import Qwen2_5_VLProcessor processor Qwen2_5_VLProcessor.from_pretrained(model_path)数据加载时会自动完成图片预处理、token化、padding等操作在数据集的getitem方法里调用processor处理文本和图片即可。要注意的是processor返回的tensor类型、维度要和模型的forward输入一致特别是多模态模型的输入格式不同版本之间可能有细微差异。4.2 LoRA参数选择与训练细节在4090上做LoRA微调时我初始的配置是LoRA的r设为32alpha设为64alpha是r的两倍这是一个常用的初始比例应用目标模块选择query、key、value、output投影层视觉编码器不参与LoRA使用bf16精度学习率为2e-4使用余弦退火调度器batch size为8梯度累积4步训练3个epoch刚开始跑的时候loss曲线下降很快前500步从2.1降到0.8左右但验证集上的指标提升不明显。仔细排查后发现问题出在LoRA的作用范围上也就是前面提到的“只修改了文本侧对视觉特征的解读方式没修改视觉特征本身”。解决方案是将视觉编码器的部分层也纳入LoRA作用范围。具体操作比较简单peft库的target_modules参数支持传入包含视觉层名称的列表。Qwen2.5-VL的视觉编码器层命名规律是visual.blocks我把visual.blocks.*.attn.qkv和visual.blocks.*.attn.proj都加进去了。这样视觉侧和文本侧同时做低秩适配验证集上原先表现不好的细粒度属性识别任务开始有明显起色。从配置参数上看可训练参数从约0.4%增加到约0.9%。这里有一个重要经验多模态模型的LoRA微调不能只盯着文本侧视觉编码器的适配范围同样关键。如果视觉特征没有被有效调整模型对目标任务中的特殊视觉模式仍然是“盲”的。但同时也不能把所有视觉层都放开因为视觉编码器参数体量大全放开会导致显存占用急剧上升而且容易过拟合。需要找到一个平衡点通常的做法是只对视觉编码器的后半部分深层做LoRA浅层保持冻结。我最后采用的是最后六层视觉block参与LoRA训练效果和性能平衡最好。4.3 4090训练过程记录24GB显存的4090训练Qwen2.5-VL如果不做任何优化batch size最多只能开到4左右。我采用batch size8、gradient_accumulation_steps4的方式相当于每16个样本更新一次梯度显存峰值大约在21GB上下。训练过程比较稳定loss从2.1降到最后0.4左右验证集准确率从77%提升到89.5%。训练总时长约6小时3个epoch总计约2400步。这个效果已经证明了微调路线是可行的。但训练过程中也遇到一个有意思的现象当r值从32降到8之后效果会有明显下降说明这个业务场景的领域知识量较大低秩表征不够用。这说明在多模态任务中不能无脑把r设为小值尤其是视觉侧的特征适配需要更大的自由度。4090上的完整跑通让我松了一口气接下来就是最难的昇腾迁移过程。5. OFT的引入从LoRA到OFT的动机与关键改造5.1 昇腾上的LoRA适配难题迁移到昇腾910B的第一个版本直接用4090上训练好的LoRA配置修改设备类型后用torch_npu跑。结果第一轮就遇到了算子不兼容的问题主要卡在视觉编码器的某些attention算子上。昇腾的CANN算子库覆盖范围虽然很广但依然有部分算子的性能极差甚至直接不支持。其中最大的瓶颈是flash attention。Qwen2.5-VL的官方实现里vision encoder的self-attention默认用的是flash attention加速这个算子在昇腾上要么不支持要么自动回退到了普通attention实现。普通attention在昇腾上跑得极慢一次前向加反向的计算时间比4090多了将近3倍。更麻烦的是某些版本的torch_npu对scaled_dot_product_attention的支持不完善会出现数值错误的情况loss直接变成NaN。排查了一整天才定位到是flash attention回退问题。解决方案是在模型初始化时关闭flash attention强制使用普通的attention实现。Qwen2.5-VL的模型初始化参数里有attn_implementation这个选项设置为eager即可。但这也带来了训练速度的明显下降昇腾上的迭代速度比4090慢了大约40%。为了弥补这个性能差距我们尝试了多种方案最后转向OFT也是考虑到OFT对视觉编码器的适配方式更“温和”也许能减少一些算子的复杂性。另一个昇腾上LoRA容易踩的坑是梯度累积和混合精度。昇腾上的自动混合精度策略和NVIDIA不一样默认情况下PyTorch的bf16在昇腾上会产生大量数值不稳定的情况。这是因为昇腾的bf16计算精度在某些算子实现上不如CUDA那么成熟存在精度截断误差累积到梯度上会导致loss在后期震荡明显。解决方案是改用fp16加动态loss scaling这个在昇腾上情绪稳定但需要额外调试loss scaling参数否则很容易出现underflow或者overflow。5.2 OFT原理回顾与选型判断在昇腾的适配过程中我们顺便调研了一下OFT在昇腾上的可行性。OFT这个名字很早就有了但在多模态领域应用不多之前主要是用在图像生成模型和diffusion模型的微调里。本质上OFT是学习一个正交变换而正交变换可以用一个上三角矩阵的QR分解或者Cayley变换来参数化保证变换的正交性。Qwen2.5-VL的模型结构里视觉编码器的每一层都有一个输出投影层OFT可以直接作用于这个投影层的权重上。相比LoRAOFT的优势在于它不改变特征向量的长度和夹角对于视觉特征这种语义高度密集的表征来说OFT的微调更安全不容易破坏预训练模型已经建立好的特征空间结构。同时OFT的可训练参数规模比LoRA更小这也在一定程度上缓解了昇腾上训练速度慢的问题。让我下决心从LoRA切到OFT的另外一个关键原因是昇腾的算子兼容性。LoRA的实现需要用到多个小的矩阵乘法和加法在昇腾上算子调度比较频繁每一步的kernel launch开销很高。而OFT的实现是少数几个较大规模的正交变换算子数量更少昇腾上的执行效率反而更好。这是一个很有意思的反转在4090上LoRA通过优化过的CUDA算子可以跑得非常快但在昇腾上由于算子调度开销更大反倒是OFT这种计算模式更高效。5.3 从LoRA切换到OFT的具体代码改造OFT的实现可以用现成的库也可以用PyTorch自己实现。我们采用的是自实现方案主要是为了灵活控制变换层的作用范围。核心代码如下import torch import torch.nn as nn import torch.nn.functional as F class OFTLayer(nn.Module): def __init__(self, dim, r, deviceNone): super().__init__() self.device device self.r r self.Q nn.Parameter(torch.randn(dim, r, devicedevice)) self.P nn.Parameter(torch.randn(r, dim, devicedevice)) def forward(self, x): # x: [batch, seq, dim] R self.get_orthogonal_matrix() return torch.matmul(x, R) def get_orthogonal_matrix(self): # Q和P的乘积经过QR分解得到正交矩阵 A torch.matmul(self.Q, self.P) Q, R torch.linalg.qr(A) # 确保构造出的矩阵是正交的 return Q[:, :self.r] Q[:, :self.r].T应用方式是找到Qwen2.5-VL模型中视觉编码器的输出投影层用OFTLayer包一层替换原来的权重计算逻辑。实现起来思路清晰但有一个细节Qwen2.5-VL的模型结构和老版本Qwen-VL不完全一样视觉编码器内部有一些层的名字变了需要打印model结构后一一确认。需要注意的关键点是正交矩阵的初始化方式。如果直接随机初始化Q和POFTLayer刚开始可能会输出非常大的数值因为正交矩阵的谱范数可能大于1导致训练初期loss激增。解决方法是让Q初始化为接近单位矩阵的结构最简单的做法是把Q初始化为单位矩阵的一小部分加少量噪声同时把P初始化为接近零的小值。这样初始阶段OFTLayer近似于恒等映射不会对预训练模型的输出产生剧烈扰动。5.4 OFT在昇腾上的训练参数与表现OFT的引入显著改善了昇腾上的训练体验。首先是速度问题OFT的算子数量少昇腾的kernel调度开销不再成为瓶颈迭代速度从LoRA的每秒0.8步提升到每秒1.1步提升约37%。其次是数值稳定性OFT的正交性质让梯度的传播更加平稳loss曲线没有出现性能波动也不再出现NaN的情况。从效果上看OFT在昇腾上的最终验证集准确率达到了91.8%比LoRA在4090上的最佳效果89.5%还高2.3个百分点。这个结果验证了OFT在视觉特征适配上的优势同时也说明昇腾平台在硬件性能上并不差关键是要找到适合的算法结构。昇腾上的OFT训练参数设置如下学习率1e-4比LoRA低因为正交变换的更新幅度相对温和不需要太大的学习率batch size8梯度累积4步训练epoch3精度fp16 动态loss scalingOFT的秩r64比LoRA的r32更大因为OFT的正交矩阵需要足够的空间来表达变换方向这些参数不是一次调出来的我前后跑了几组对比实验。最开始用和LoRA一样的2e-4学习率发现loss在前200步下降得非常快但验证集指标在600步左右就开始饱和后续几乎不涨。后来将学习率降到1e-4训练更加平缓最终效果反而更好。这说明OFT的收敛特性确实和LoRA不同正交约束本身就是一种正则化不需要靠学习率来抑制过拟合。还有一个参数值得关注OFT的r值。r值是正交矩阵的秩决定了变换能够表达的空间维度。我测试了r32、64、128三组r128时训练速度明显变慢但效果提升有限r32时训练速度快但效果差了1.5个点左右。r64是一个性价比较高的选择这也侧面说明了视觉特征适配需要一定的参数量作为支撑但也不是越多越好。6. 双平台对比实验性能、效果与稳定性分析6.1 训练时间与资源消耗对比为了做一个公平的对比我把LoRA和OFT分别放在4090和昇腾910B上各跑了一遍完整的3个epoch训练。以下是一组实测数据配置平台单步耗时秒训练总时长小时峰值显存/NPU内存GBLoRA40900.356.221.3LoRA昇腾910B0.8014.523.8OFT40900.559.822.1OFT昇腾910B0.6211.224.0从表格中可以清晰看到几个趋势OFT在4090上比LoRA慢多了正交矩阵分解的计算但在昇腾上比LoRA快算子调度更少。昇腾上的显存占用比4090略高主要是由于缺乏优化的flash attention算子attention部分需要更多的中间显存来存储attention矩阵。6.2 效果对比与业务指标效果对比维度包括三个方面整体准确率、细粒度属性颜色、瑕疵的F1值、以及推理速度。这决定了模型能否真正落地到业务场景中。配置整体准确率颜色F1瑕疵F1推理速度图/秒原始模型未微调77.2%70.1%74.5%2.8LoRA409089.5%86.4%88.1%2.7OFT409090.6%88.0%89.3%2.6OFT昇腾910B91.8%89.2%90.5%2.5OFT在两个平台上的效果都比LoRA好且在昇腾上效果最好。这个现象我推测试与浮点精度有关昇腾上用fp16训练时LoRA的低秩矩阵更新对数值波动更加敏感而OFT的正交归一化保障了数值的稳定性使得模型能够从同样的训练数据中更充分地学习到有效特征。当然这个猜测还需要更多实验验证但从实际结果来看OFT在昇腾平台上的表现确实比LoRA更加可靠。推理速度方面微调后模型比原始模型略慢主要原因是微调后模型的输出分布发生了变化导致生成时解码的步数增多这个问题在LoRA和OFT上都存在不属于平台差异。6.3 稳定性表现训练稳定性的一个重要指标是loss曲线是否有异常波动。LoRA在4090上的loss曲线整体平滑在昇腾上则出现了几次小幅度的跳变峰值大约在0.05左右。这些跳变在验证集上对应了准确率的短暂回调影响不大但需要留意。OFT在两种平台上的loss曲线都相当平稳没有出现跳变这也是正交变换对梯度流控制的直观表现。另一个和训练稳定相关的指标是梯度的范数。我记录了每100步的梯度范数变化LoRA在昇腾上的梯度范数波动比较大范围在0.8到2.7之间波动OFT则相对稳定基本保持在1.2到1.6之间。这说明OFT的优化路径更可控不容易出现梯度爆炸或梯度消失的情况。对于昇腾这种数值精度相对CUDA脆弱一些的平台来说这种稳定性非常有价值。7. 踩坑实录昇腾与CUDA平台差异导致的典型问题7.1 算子兼容性flash attention和scaled_dot_product_attention前面已经提到了flash attention在昇腾上的问题这里展开详细说。Qwen2.5-VL的官方代码在初始化模型时会根据传入的attn_implementation参数来决定使用什么attention实现。默认情况下它可能自动选择flash attention这在4090上没有问题的但因为昇腾的算子库没有完全对应flash attention的高效kernel要么回退可能回退失败导致报错要么以极低的效率运行。遇到的报错信息大致是NotImplementedError或者kernel launch failed不同版本报错不一样。解决办法是显式指定attn_implementationeagermodel Qwen2_5_VLForConditionalGeneration.from_pretrained( model_path, torch_dtypetorch.bfloat16, attn_implementationeager )需要注意的是这个参数在4090上不设置也没关系但在昇腾上必须显式设置否则很可能会跑着跑着突然报错中断非常影响训练任务的连续性。另外scaled_dot_product_attention这个PyTorch的算子也需要注意。即使你强制了eager模式Qwen2.5-VL的vision encoder内部某些函数可能还会调用SDPA所以在昇腾需要用环境变量来强制禁用它。这个在不同版本上的做法不太一样我最后是通过源码替换的方式把模型代码里调用SDPA的地方改成标准的attention实现才彻底解决的。7.2 pin_memory与数据加载问题昇腾的torch_npu对pin_memoryTrue的支持不完善之前跑LoRA的时候把DataLoader的pin_memory设置为True结果在昇腾上不定期地抛出RuntimeError: unrecognized error。这个错最坑的是不一定在训练刚开始就报而是训练过程中某个随机时刻突然中断白白损失好几个小时的训练进度。排查了很久才定位到是pin_memory的问题。后来写了一个平台检测逻辑统一设置use_pinned device ! npu train_loader DataLoader(dataset, batch_sizeargs.batch_size, shuffleTrue, pin_memoryuse_pinned)把pin_memory置为False之后这个问题再也没出现过。如果你的昇腾版本有更新可以先尝试pin_memoryTrue如果稳定则保留但建议做好定时保存checkpoint的习惯避免问题出现时损失过大。7.3 混合精度bf16在昇腾上的数值稳定性前面已经简单说过昇腾上bf16的训练容易出现梯度不稳定问题。这里补充一个具体现象LoRA在昇腾上用bf16训练到第800步左右loss突然从0.5跳到了0.9然后又在100步内恢复。一开始以为是学习率的问题排查了很久发现是bf16的舍入误差累积导致某个维度的梯度过大激活了模型内部的自注意力机制对某些异常值放大从而引起loss瞬间变大。fp16加动态loss scaling能解决这个问题主要原因是fp16在昇腾上的实现相对更成熟而且动态loss scaling机制可以自动调整缩放因子避免精度不足导致的梯度溢出或者梯度消失。具体操作用accelerate库的混合精度设置mixed_precisionfp16即可。但fp16也有一点需要注意它对学习率更加敏感。在bf16下合适的学习率放到fp16下可能会导致前几步loss激增因为fp16的表示范围相对有限过大的学习率容易让梯度更新一步跨到数值溢出区域。如果遇到这个问题可以适当降低学习率同时检查loss scaler的初始值是否为合理的默认值通常是2的16次方。7.4 多卡训练的环境变量与通信问题昇腾上的多卡训练比4090的NCCL多卡训练更容易出问题。两个平台的通信库不同4090用NCCL昇腾用HCCL。在代码层面accelerate库会统一处理不需要显式区分。但在环境变量层面昇腾有一些额外的设置需要完成。最典型的错误是HCCL_CONNECT_TIMEOUT默认值太小导致多卡之间通信握手超时。解决办法是在启动训练前显式设置环境变量export HCCL_CONNECT_TIMEOUT120 export HCCL_OP_TIMEOUT120还有一个是昇腾的HCCL在单机多卡时需要确保所有卡的visible_devices设置正确不能简单地用npu:0、npu:1的方式引用否则通信可能出现死锁。我这次项目主要跑单卡训练没有深入多卡优化但在实际生产环境中多卡训练大概率会遇到这部分经验是后来在另一台8卡昇腾机器上验证过的。7.5 版本兼容性陷阱这里整理一下需要严格对应匹配的版本清单PyTorch和torch_npu版本必须严格对应一个PyTorch的patch版本对应一个torch_npu的releaseCANN版本和固件版本必须匹配安装时先装固件驱动再装CANNtransformers和Qwen2.5-VL的代码有版本对应关系某些transformers旧版本无法load新模型我在昇腾上遇到一个隐蔽的问题transformers版本太新对Qwen2.5-VL的模型结构做了改动导致之前用的processor在加载时返回的数据格式变化训练出来的模型在推理时出现了输出乱码。这个问题的排查耗费了一天半时间最后回退到固定版本才解决。建议在项目一开始就制定一个requirements.txt并锁定所有版本号不要随手升级任何包否则很容易在两个平台之间出现不一致。8. 总结与经验分享项目走到这里回头复盘整个流程有几个经验想特别分享给正在做类似项目的朋友。第一多模态模型的微调视觉编码器的作用范围必须被认真考虑。无论你选择LoRA、OFT还是其他参数高效微调方法只微调文本侧而忽略视觉侧大概率会在细粒度视觉任务上碰壁。参数高效微调方法的选择也很关键LoRA适合文本特征适配OFT更适合视觉特征的空间调整选对方法往往比调参更重要。第二跨平台开发时优先保证在两个平台上都能稳定跑通最简版本然后再逐步增加复杂度。如果我先在昇腾上解决所有兼容性问题再开始训练可能要耗费两周时间才能看到第一个有效结果。而先在4090上验证路线可行性再花精力做昇腾适配整体效率高很多。实际项目周期中这个策略帮我们节省了至少一周的无效劳动。第三昇腾平台并没有想象中那么难用关键是找对思路。LoRA在昇腾上因为算子调度频繁、bf16精度问题等因素体验并不好。OFT这种计算模式反而能发挥昇腾的算力优势最终效果还更好。这是硬件平台特性和算法结构相互作用的一个典型例证多做针对性适配和实验国产卡完全可以支撑高质量的模型微调任务。最后说一个实用的小技巧昇腾上训练时建议开启自动checkpoint保存间隔设置为每200步一次。昇腾的算子兼容性问题会在意想不到的时候出现即使前面都跑通了某个batch的数据可能会导致一个特殊算子触发异常。有了定期checkpoint即使中断也只是损失几分钟的训练时间不至于从头再来。这个习惯帮我避免了好几次重大的进度损失非常值得养成。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑