
1. 为什么要在2080 Ti上折腾ms-swift加unsloth手里还攥着2080 Ti这张卡的人大概率都经历过同一个心理过程看着别人讨论A100、H100自己默默打开任务管理器发现11GB显存里系统已经吃掉了一部分剩下那点空间连个7B模型的全量微调都塞不进去。但偏偏Qwen3-VL这种多模态模型又很诱人能看图能理解文字做图文问答、文档解析、界面理解这些任务都挺实用。问题就出在多模态模型的视觉编码器本身就占显存再加上语言模型部分哪怕只是做LoRA微调显存也紧得让人头皮发麻。我这次要记录的就是在2080 Ti这个老平台上把ms-swift和unsloth接起来跑Qwen3-VL微调的完整过程。ms-swift是魔搭社区那套训练框架封装得比较完整支持各种微调方式和模型结构unsloth则是以显存优化和训练加速出名的方案它通过重写部分算子、优化梯度计算路径能在有限显存下把训练跑起来。把这两个东西接在一起核心目的就一个让2080 Ti这种老卡也能参与Qwen3-VL的微调实验而不是只能干看着。这篇文章适合谁看如果你手里有20系或者30系显卡显存不大但又想跑多模态模型的微调如果你已经在用ms-swift但发现显存不够想试试unsloth能不能救一救或者你只是好奇这两个框架怎么配合那这篇笔记应该能给你一些直接能抄的配置和踩坑记录。我会把环境搭建、依赖冲突处理、显存分配策略、训练参数设置、常见报错排查都写清楚尽量让你少走弯路。需要提前说明的是2080 Ti是Turing架构不支持BF16原生加速也没有FlashAttention-2的完整支持这些硬件限制会直接影响训练配置的选择。后面我会具体讲怎么在FP16模式下尽量稳住训练以及哪些参数必须调低才能避免OOM。2. 环境搭建与依赖版本锁定2.1 基础环境选择与CUDA版本考量2080 Ti这张卡驱动和CUDA版本的选择其实挺关键。太新的CUDA版本对Turing架构的支持反而不一定好太老的又可能缺少某些算子。我实测下来CUDA 11.8配合PyTorch 2.1.x是比较稳的组合。PyTorch 2.2以上虽然也能跑但在某些自定义算子编译时会遇到兼容性问题尤其是unsloth里面那些手写的CUDA kernel。Python版本建议用3.103.11也可以但有些依赖包的wheel还没跟上。conda建环境的时候记得指定python3.10然后先装PyTorch再装其他依赖。顺序很重要因为unsloth和ms-swift都对torch版本有要求如果先装了别的包把torch版本带偏了后面再改会很麻烦。conda create -n swift-unsloth python3.10 -y conda activate swift-unsloth pip install torch2.1.2 torchvision0.16.2 torchaudio2.1.2 --index-url https://download.pytorch.org/whl/cu118装完torch之后验证一下CUDA是否可用顺便看看显卡识别情况import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0)) print(torch.cuda.get_device_properties(0).total_memory / 1024**3)如果这里输出的是2080 Ti和约11GB显存说明基础环境没问题。注意total_memory显示的是理论值实际可用会少一些因为系统显示输出和CUDA上下文都会占一部分。2.2 ms-swift与unsloth的安装顺序及版本匹配这两个框架的安装顺序有讲究。我的建议是先装ms-swift再装unsloth因为unsloth安装时会检查torch和transformers版本如果先装unsloth它可能会把transformers锁在一个特定版本导致ms-swift的依赖解析出问题。ms-swift的安装直接用pip就行pip install ms-swift -U但这里有个坑ms-swift默认会拉最新版的transformers而unsloth对transformers版本有比较严格的要求。我实测下来transformers 4.40到4.44之间比较稳太新的版本unsloth的patch可能还没适配。所以装完ms-swift之后先看一下它拉了什么版本的transformerspip show transformers | grep Version如果版本太新比如4.46以上建议手动降下来pip install transformers4.43.3然后再装unsloth。unsloth的安装命令根据你的CUDA版本和torch版本有不同的选择2080 Ti用CUDA 11.8的话pip install unsloth如果直接装遇到编译错误可以试试从源码装pip install githttps://github.com/unslothai/unsloth.git装完之后验证unsloth是否能正常导入并且检查它是否识别到了你的GPUimport unsloth print(unsloth.__version__) from unsloth import FastLanguageModel print(unsloth imported successfully)注意unsloth在导入时会自动patch一些transformers的类如果这时候报错说某个模块找不到大概率是transformers版本不匹配。解决办法就是回退transformers版本而不是去改unsloth的源码。2.3 关键依赖的版本对照表为了让你少试几次我把这次跑通的版本组合列出来依赖包版本说明Python3.10.133.11也可但部分wheel缺失PyTorch2.1.2cu1182.2以上自定义算子编译易出错transformers4.43.3与unsloth兼容性最好peft0.11.1ms-swift和unsloth都依赖trl0.9.6版本太高会与ms-swift冲突accelerate0.33.0分布式训练相关bitsandbytes0.43.14bit量化需要ms-swift2.4.0太老的版本不支持Qwen3-VLunsloth2024.10具体版本号随日期变化这个组合是我反复试出来的不一定是最新的但胜在稳定。如果你用更新的版本跑通了那更好如果遇到莫名其妙的报错可以先回退到这个组合试试。3. Qwen3-VL模型加载与显存优化策略3.1 模型结构特点与显存占用分析Qwen3-VL和纯文本模型最大的区别在于它有一个视觉编码器。这个编码器通常是ViT结构参数量虽然不如语言模型大但它在处理图像时会生成大量的视觉token这些token会进入语言模型参与注意力计算导致显存占用飙升。一张1024x1024的图片经过patch分割后可能产生上千个视觉token再加上文本token序列长度很容易就上到几千。在2080 Ti的11GB显存下如果直接加载Qwen3-VL的7B版本做全量微调那是不可能的。光是模型权重用FP16加载就要14GB左右还没算梯度和优化器状态。所以必须走量化加LoRA的路线。4bit量化可以把模型权重的显存占用压到原来的四分之一左右7B模型大概3.5GB到4GB再加上LoRA的可训练参数、梯度、优化器状态以及视觉编码器的开销整体能控制在9GB到10GB之间勉强能跑起来。但这里有个问题unsloth的4bit量化主要针对语言模型部分做了优化视觉编码器部分的支持需要额外确认。我实测发现如果直接把整个Qwen3-VL丢给unsloth的FastLanguageModel加载视觉编码器部分可能不会被正确量化导致显存还是超。解决办法是分开处理语言模型部分用unsloth的4bit加载视觉编码器部分用bitsandbytes单独量化或者干脆冻结视觉编码器只训练语言模型部分的LoRA。3.2 使用unsloth加载4bit量化模型unsloth加载模型的方式和transformers不太一样它有自己的FastLanguageModel接口。对于Qwen3-VL加载代码大概长这样from unsloth import FastLanguageModel import torch model, tokenizer FastLanguageModel.from_pretrained( model_nameQwen/Qwen3-VL-7B-Instruct, max_seq_length2048, dtypetorch.float16, load_in_4bitTrue, device_mapauto, )这里有几个参数需要解释。max_seq_length设成2048是因为2080 Ti的显存有限序列太长注意力矩阵会爆。dtype用float16而不是bfloat16因为Turing架构不支持BF16的原生加速用BF16反而会走模拟路径速度更慢。load_in_4bit开启4bit量化这是省显存的关键。device_map用auto让accelerate自动分配但实际在单卡上就是全部放GPU。加载完之后需要给模型加上LoRA适配器model FastLanguageModel.get_peft_model( model, r16, target_modules[q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj], lora_alpha16, lora_dropout0, biasnone, use_gradient_checkpointingunsloth, random_state3407, )r16是LoRA的秩这个值越大可训练参数越多显存占用也越大。在2080 Ti上我建议不要超过3216是比较平衡的选择。target_modules覆盖了注意力层和MLP层这样微调效果会好一些但也会增加可训练参数。如果显存实在紧张可以只保留q_proj和v_proj两个模块。use_gradient_checkpointingunsloth是unsloth的优化点之一它用了一种更高效的梯度检查点实现比transformers原生的能再省一些显存。但要注意梯度检查点会增加计算时间相当于用时间换空间。3.3 视觉编码器的处理方式Qwen3-VL的视觉编码器部分在unsloth加载时可能不会被自动量化。你可以通过检查模型各部分的dtype来确认for name, param in model.named_parameters(): if visual in name: print(name, param.dtype, param.shape) break如果发现视觉编码器的参数还是float16而不是int4那就需要手动处理。最简单的办法是冻结视觉编码器只训练语言模型部分的LoRAfor name, param in model.named_parameters(): if visual in name: param.requires_grad False冻结之后视觉编码器的参数不需要计算梯度优化器状态也不占显存能省下不少空间。代价是视觉理解能力不会在微调中提升但对于很多任务来说Qwen3-VL预训练的视觉编码器已经够用了微调主要调整的是语言模型部分对视觉信息的利用方式。如果确实需要微调视觉编码器那就得用bitsandbytes的量化配置单独处理或者把视觉编码器也纳入LoRA的范围。但这样显存会非常紧张2080 Ti上基本跑不动建议还是冻结。4. ms-swift训练配置与unsloth集成4.1 ms-swift的配置文件结构ms-swift支持用命令行参数或者配置文件来指定训练参数。我习惯用配置文件因为参数多了之后命令行太长容易写错。一个典型的配置文件结构大概是这样的{ model_type: qwen3-vl, model_id_or_path: Qwen/Qwen3-VL-7B-Instruct, template: qwen3-vl, dataset: your_dataset, output_dir: ./output, max_length: 2048, num_train_epochs: 3, per_device_train_batch_size: 1, gradient_accumulation_steps: 8, learning_rate: 1e-4, lr_scheduler_type: cosine, warmup_ratio: 0.05, logging_steps: 10, save_steps: 200, fp16: true, bf16: false, gradient_checkpointing: true, use_unsloth: true, lora_rank: 16, lora_alpha: 16, lora_dropout: 0.0, target_modules: [q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj] }这里的关键参数是use_unslothms-swift在较新的版本里已经支持直接调用unsloth作为后端。但要注意这个集成可能不是所有版本都有如果你的ms-swift版本里没有这个参数那就需要手动在代码里把unsloth的模型加载逻辑接进去。per_device_train_batch_size设成1是因为显存实在有限只能靠gradient_accumulation_steps来增大等效batch size。8步累积相当于batch size为8对于LoRA微调来说基本够用。如果显存还有余量可以试试batch size为2但要做好OOM的准备。4.2 把unsloth的模型接入ms-swift训练流程如果ms-swift的use_unsloth参数不生效或者你想更精细地控制可以手动把unsloth加载的模型传给ms-swift的Trainer。大概的思路是先用unsloth加载模型和tokenizer然后构造ms-swift的Trainer时把模型传进去。from swift import Swift, LoRAConfig from swift.trainers import Seq2SeqTrainer from unsloth import FastLanguageModel # 用unsloth加载模型 model, tokenizer FastLanguageModel.from_pretrained( model_nameQwen/Qwen3-VL-7B-Instruct, max_seq_length2048, dtypetorch.float16, load_in_4bitTrue, ) # 配置LoRA lora_config LoRAConfig( r16, lora_alpha16, target_modules[q_proj, k_proj, v_proj, o_proj], lora_dropout0.0, ) # 用swift包装模型 model Swift.prepare_model(model, lora_config) # 构造Trainer trainer Seq2SeqTrainer( modelmodel, tokenizertokenizer, argstraining_args, train_datasettrain_dataset, ) trainer.train()这里需要注意的是unsloth的FastLanguageModel加载出来的模型已经做了一些patch再用Swift.prepare_model包装时可能会冲突。我实测发现如果unsloth已经给模型加了LoRA就不要再重复加。所以要么用unsloth的get_peft_model要么用swift的LoRAConfig不要两个都用。4.3 训练参数调优与显存监控在2080 Ti上跑训练显存监控是必须的。我一般开两个终端一个跑训练一个用nvidia-smi实时看显存watch -n 1 nvidia-smi如果发现显存快满了可以调这几个参数降低max_length、减小lora_rank、增大gradient_accumulation_steps同时减小batch_size、开启更激进的梯度检查点。学习率方面LoRA微调一般用1e-4到5e-4之间。我这次用的是1e-4配合cosine调度和0.05的warmup训练过程比较稳。如果loss震荡厉害可以降到5e-5试试。还有一个容易忽略的点是dataloader的num_workers。在Windows上或者某些Linux环境下num_workers设大了反而会拖慢速度甚至卡死。我一般设成2或者4根据CPU核心数来定。5. 实操过程中遇到的坑与排查记录5.1 常见报错与解决方法速查表报错信息可能原因解决方法CUDA out of memory显存不足降低batch_size、max_length开启梯度检查点ImportError: cannot import name xxx from transformerstransformers版本不匹配回退到4.43.3RuntimeError: expected scalar type Half but found Float混合精度配置冲突检查fp16和bf16是否同时开启unsloth import error: no kernel imageCUDA架构不匹配确认CUDA版本和显卡架构20系用cu118Loss is nan学习率过高或数据有问题降低学习率检查数据中是否有空标签训练速度极慢梯度检查点开销大或num_workers设置不当调整num_workers确认没有走CPU回退5.2 几个让我折腾了很久的问题第一个是unsloth和ms-swift的transformers版本冲突。ms-swift 2.4.0要求transformers4.40但unsloth当时只支持到4.43。我一开始装了4.46结果unsloth导入时报了一堆patch失败的错误。后来降到4.43.3才正常。这个问题的教训是不要盲目追求最新版本尤其是当你在用一个以上框架的时候版本兼容性比新特性重要得多。第二个是视觉编码器的显存占用。我一开始以为4bit量化会把整个模型都压下去结果发现视觉编码器部分还是FP16导致显存超了。后来通过冻结视觉编码器解决。如果你也需要微调视觉部分那可能得考虑用更小的模型或者更激进的量化方案。第三个是梯度检查点和unsloth的兼容性。unsloth的use_gradient_checkpointingunsloth确实能省显存但它和ms-swift的某些训练逻辑有冲突表现为训练几个step之后loss突然变成nan。我换成普通的梯度检查点就是transformers原生的那种之后就没这个问题了。所以如果你也遇到loss异常可以试试关掉unsloth的梯度检查点优化。5.3 训练速度与显存占用的实测数据为了让你有个直观感受我记录了一组实测数据。模型是Qwen3-VL-7B4bit量化LoRA rank16max_length2048batch_size1gradient_accumulation_steps8。配置项数值显存峰值9.8GB单step耗时约3.2秒每秒处理token数约640训练1个epoch1000条数据约1.5小时GPU利用率85%到95%之间波动这个速度在2080 Ti上算是可以接受的了。如果你把max_length降到1024显存能降到8GB左右速度也能提升到2.5秒每step。但序列太短可能会截断多模态输入需要根据你的数据情况权衡。6. 一些实用的经验总结与后续扩展思路6.1 2080 Ti跑多模态微调的边界在哪里经过这次折腾我对2080 Ti的能力边界有了更清晰的认识。7B级别的多模态模型4bit量化加LoRA微调在11GB显存下是可行的但前提是冻结视觉编码器、序列长度不超过2048、batch size为1。如果你要微调13B以上的模型或者需要更长的序列那2080 Ti基本没戏不是配置能解决的问题是物理显存不够。另一个限制是训练速度。2080 Ti没有BF16加速也没有FlashAttention-2的完整支持所以训练速度比30系、40系慢不少。如果你只是做实验验证想法这个速度可以接受如果要跑大规模数据建议还是租用云GPU或者升级硬件。6.2 可以继续尝试的几个方向第一个方向是试试QLoRA的变体比如用更低的bit数或者不同的量化策略。bitsandbytes支持NF4和FP4两种4bit量化NF4在理论上对正态分布的权重更友好可以试试看效果差异。第二个方向是优化数据加载流程。多模态数据涉及图片读取和预处理如果dataloader成为瓶颈可以预先把图片处理成特征向量存下来训练时直接读特征省去重复的图片解码时间。第三个方向是试试梯度累积和学习率调度的不同组合。我这次用的是cosine加warmup你也可以试试constant或者linear调度看看loss曲线有什么不同。6.3 关于unsloth desktop和安装的一些补充unsloth最近推出了desktop版本把环境配置和模型管理做成了图形界面对新手更友好。但如果你已经熟悉命令行直接用pip安装unsloth包就行没必要额外装desktop。安装时如果遇到网络问题可以试试用国内镜像源或者从GitHub release页面下载wheel文件手动安装。另外unsloth的GitHub仓库里有一些示例notebook包括Qwen系列模型的微调示例。虽然不一定有Qwen3-VL的专门示例但Qwen2-VL的示例可以参考结构上差别不大。遇到问题时先去GitHub issues里搜一下大概率有人已经遇到过了。我在实际使用中发现unsloth对模型结构的假设比较强它默认你用的是标准的Llama式结构。Qwen3-VL虽然大体遵循这个结构但视觉编码器部分是个例外。所以如果你要改unsloth的源码来适配重点看它怎么处理vision_model部分可能需要手动跳过一些patch。最后再分享一个小技巧训练之前先用少量数据跑一个epoch确认整个流程能跑通、loss能正常下降再上全量数据。这样能避免跑了几个小时才发现配置有问题。我一般会准备一个100条左右的小数据集做冒烟测试确认没问题再换正式数据。