
1. 为什么要在 2080 Ti 上折腾 ms-swift 加 unsloth 这套组合手里还攥着 2080 Ti 的人大概率都经历过同一个心理过程看着别人用 4090、A100 跑多模态大模型微调自己这张 11GB 显存的老卡只能干瞪眼。但实际情况是只要你选对框架组合2080 Ti 依然能在 Qwen3-VL 这类视觉语言模型上跑出可用的微调效果。我这次要记录的就是把ms-swift和unsloth拼在一起在 2080 Ti 上微调 Qwen3-VL 的完整过程。先说清楚这三个东西各自是什么角色。ms-swift是魔搭社区推出的一套大模型微调框架它的优势在于对国内模型生态支持好Qwen 系列、GLM 系列、InternLM 系列都能直接拉起来训而且封装了 LoRA、QLoRA、全参微调等多种模式命令行和 Python API 都挺顺手。unsloth则是一个主打极致省显存 提速的微调加速库它通过手写 Triton 内核重写了反向传播里的关键算子官方宣称能把训练速度提升 2 倍左右、显存占用降低 50% 以上。Qwen3-VL是通义千问的视觉语言模型能同时处理图像和文本输入做图文问答、OCR 增强、图表理解这类任务。那为什么要把 ms-swift 和 unsloth 接在一起因为单独用 ms-swift它在 2080 Ti 这种老架构卡上的显存优化没那么激进Qwen3-VL 这种带视觉编码器的模型一加载光模型权重就能把 11GB 吃掉大半再算上激活值和优化器状态很容易 OOM。而 unsloth 的省显存能力是实打实的把它作为 ms-swift 的底层加速后端接进去就能在保留 ms-swift 易用性的同时拿到 unsloth 的显存红利。这里有个关键前提得先讲明白2080 Ti 是 Turing 架构算力 7.5不支持 bf16 原生加速也不支持 FlashAttention-2 的完整特性。这意味着你在配置里必须用 fp16 而不是 bf16否则要么报错要么精度炸掉。unsloth 对 Turing 卡的支持是有的但需要走它专门为老卡准备的兼容路径不能照搬 4090 上的配置。这一点后面会详细展开。适合读这篇的人手上有 20 系或更老显卡、想跑多模态微调但预算有限、已经会用 ms-swift 基础命令、听说过 unsloth 但没实际接过的人。如果你连 LoRA 是什么都还不清楚建议先去补一下参数高效微调的基础概念不然下面的配置项会让你有点懵。2. 环境搭建2080 Ti 专属的依赖版本坑2.1 驱动与 CUDA 版本的硬性约束2080 Ti 能用的 CUDA 版本上限取决于你的驱动。我这张卡刷的是 535 系列驱动最高支持 CUDA 12.2。但这里有个反直觉的点不是 CUDA 版本越新越好。unsloth 的 Triton 内核在 CUDA 12.1 和 12.2 上编译最稳定12.3 以上在 Turing 架构上偶尔会出现内核编译失败的情况。我实测下来CUDA 12.1 PyTorch 2.3.1这个组合在 2080 Ti 上最稳。安装 PyTorch 的时候千万别直接pip install torch那样会拉到最新版很可能带的是 CUDA 12.4 的轮子跟你的驱动对不上。正确做法是去 PyTorch 官网找对应 CUDA 12.1 的安装命令pip install torch2.3.1 torchvision0.18.1 torchaudio2.3.1 --index-url https://download.pytorch.org/whl/cu121装完之后验证一下import torch print(torch.__version__) print(torch.version.cuda) print(torch.cuda.get_device_capability()) # 应该输出 (7, 5)get_device_capability()返回(7, 5)就说明 PyTorch 正确识别了 Turing 架构。如果返回的是别的值后面 unsloth 的兼容判断会出错。2.2 unsloth 安装别用默认命令unsloth 的官方安装命令是pip install unsloth但这个默认包会拉最新版而最新版对 Turing 架构的支持是能用但不保证。我建议锁定一个已知稳定的版本pip install unsloth[cu121-torch231] githttps://github.com/unslothai/unsloth.git这个cu121-torch231的 extra 标记会帮你装对应 CUDA 12.1 和 PyTorch 2.3.1 的预编译依赖。装完之后还要单独确认一下xformers和trl的版本unsloth 对这两个库的版本很敏感pip install xformers0.0.27 trl0.9.6 peft0.12.0注意trl版本高于 0.9.6 时unsloth 的SFTTrainer补丁可能打不上会报AttributeError。这个坑我踩了两次才定位到。2.3 ms-swift 的安装与版本对齐ms-swift 的安装相对简单pip install ms-swift -U但要注意ms-swift 内部也依赖peft、transformers、accelerate这几个库如果版本跟 unsloth 要求的不一致会出现装了 unsloth 之后 ms-swift 反而跑不起来的情况。我的做法是先装 unsloth 及其依赖再装 ms-swift并且用pip check检查冲突pip check如果提示peft版本冲突手动降级到 unsloth 要求的版本即可。transformers建议锁在 4.44.0 左右太新的版本 Qwen3-VL 的 processor 接口有变动ms-swift 可能还没跟上。2.4 验证 unsloth 是否真的在 2080 Ti 上生效装完之后跑一段最小验证代码确认 unsloth 的加速内核真的加载了from unsloth import FastLanguageModel import torch model, tokenizer FastLanguageModel.from_pretrained( model_nameunsloth/Qwen2.5-0.5B, max_seq_length512, dtypetorch.float16, # 2080 Ti 必须用 fp16 load_in_4bitTrue, ) print(unsloth loaded:, hasattr(model, for_training))如果这段能跑通且不报 Triton 编译错误说明基础环境 OK。注意这里我故意用了一个小模型做验证因为 Qwen3-VL 加载慢先用小模型确认环境能省不少时间。3. 把 unsloth 接进 ms-swift 的三种可行路径3.1 路径一用 ms-swift 的--use_unsloth开关最省事但限制多ms-swift 其实内置了对 unsloth 的支持命令行里加一个--use_unsloth true就能启用。这是最省事的路径swift sft \ --model_type qwen3-vl-7b \ --model_id_or_path Qwen/Qwen3-VL-7B-Instruct \ --use_unsloth true \ --dataset your_dataset \ --torch_dtype float16 \ --max_length 2048 \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 8 \ --learning_rate 1e-4 \ --lora_rank 16 \ --lora_alpha 32 \ --num_train_epochs 1 \ --output_dir ./output但这条路径的限制在于ms-swift 的 unsloth 集成主要针对纯文本模型Qwen3-VL 这种带视觉塔的多模态模型视觉编码器部分不会走 unsloth 的优化。也就是说你能省下语言模型部分的显存但视觉编码器那几百 MB 到 1GB 多的显存还是照常占。对于 2080 Ti 来说这已经能救命了但如果你还想更极致就得走下面两条路。3.2 路径二手动 patch让视觉塔也走 unsloth这条路需要你改一点源码。核心思路是在 ms-swift 加载模型之后、开始训练之前用 unsloth 的FastVisionModel替换掉原来的模型加载逻辑。具体做法是写一个自定义的训练脚本而不是用命令行from unsloth import FastVisionModel from swift import Swift, LoRAConfig import torch model, processor FastVisionModel.from_pretrained( Qwen/Qwen3-VL-7B-Instruct, load_in_4bitTrue, use_gradient_checkpointingunsloth, max_seq_length2048, dtypetorch.float16, ) model FastVisionModel.get_peft_model( model, finetune_vision_layersTrue, # 关键视觉层也参与 LoRA finetune_language_layersTrue, finetune_attention_modulesTrue, finetune_mlp_modulesTrue, r16, lora_alpha32, lora_dropout0.05, target_modules[q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj], )这里finetune_vision_layersTrue是重点。默认情况下 unsloth 为了省显存会把视觉层冻住但 Qwen3-VL 做图文任务时视觉层的适配往往决定了最终效果。打开它之后显存会多占一些2080 Ti 上要配合 4bit 量化才能扛住。3.3 路径三混合方案文本走 unsloth、视觉走 ms-swift 原生如果你发现路径二在 2080 Ti 上还是 OOM可以退一步语言模型部分用 unsloth 加载视觉编码器用 ms-swift 原生的加载方式两边拼起来。这个方案实现起来最麻烦需要你手动处理 state_dict 的映射但显存占用是最低的。我一般只在 11GB 卡上跑 7B 以上模型时才会用这招。三条路径的取舍可以看这个表路径显存节省实现难度视觉层可训适用场景路径一中等低否快速验证、纯文本任务路径二高中是图文任务、追求效果路径三最高高部分极限显存、7B 模型4. Qwen3-VL 在 11GB 显存下的参数配置实战4.1 量化策略4bit 是底线不是选项2080 Ti 的 11GB 显存加载 Qwen3-VL-7B 的 fp16 权重就要 14GB 左右直接爆。所以4bit 量化不是可选项是必选项。unsloth 用的是 bitsandbytes 的 NF4 量化加载后模型权重大概压到 4GB 出头加上视觉编码器和激活值勉强能塞进 11GB。配置上要注意bnb_4bit_compute_dtype这个参数。很多人默认用 bf16但 2080 Ti 不支持必须改成 fp16from transformers import BitsAndBytesConfig import torch bnb_config BitsAndBytesConfig( load_in_4bitTrue, bnb_4bit_quant_typenf4, bnb_4bit_compute_dtypetorch.float16, # 关键 bnb_4bit_use_double_quantTrue, )use_double_quantTrue是二次量化能再省一点显存代价是精度损失微乎其微建议开着。4.2 序列长度与 batch size 的平衡Qwen3-VL 处理图像时一张图会被切成多个 patch每个 patch 相当于若干个 token。一张 448x448 的图大概会产生 256 个视觉 token加上文本 token序列长度很容易冲到 2048 以上。在 2080 Ti 上我建议max_length设 2048不要贪心设 4096per_device_train_batch_size设 1gradient_accumulation_steps设 8 到 16这样等效 batch size 是 8 到 16训练稳定性够用显存也不会爆。如果你把max_length提到 4096即使 batch size 是 1激活值也会让显存多占 2GB 以上2080 Ti 直接顶不住。4.3 gradient checkpointing 的正确打开方式unsloth 提供了一个自己的 gradient checkpointing 实现比 PyTorch 原生的更省显存。启用方式是model, processor FastVisionModel.from_pretrained( ..., use_gradient_checkpointingunsloth, # 不是 True )注意这里传的是字符串unsloth而不是布尔值True。传True会走 PyTorch 原生实现省显存效果差一截。这个细节官方文档里写得比较隐蔽我是翻源码才确认的。4.4 LoRA 秩与 target modules 的选择LoRA 的秩rank决定了可训练参数量。2080 Ti 上我建议 rank 设 16alpha 设 32。rank 再高可训练参数变多优化器状态占的显存也会涨。target modules 方面Qwen3-VL 的注意力层和 MLP 层都要覆盖target_modules [ q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj, ]视觉塔里的qkv和proj层是否要加进 target modules取决于你的任务。如果做的是 OCR 或图表理解视觉层微调收益明显如果只是图文问答冻住视觉层也能有不错效果还能省显存。5. 训练过程中那些文档不会告诉你的坑5.1 Triton 内核编译失败Turing 架构的专属问题unsloth 的加速内核是用 Triton 写的而 Triton 在 Turing 架构上编译时偶尔会报ptxas fatal: Unsupported .version之类的错误。这个问题的根源是 Triton 默认按较新的 PTX 版本编译而 2080 Ti 的驱动只支持到某个版本。解决办法是设置环境变量export TRITON_PTXAS_PATH/usr/local/cuda/bin/ptxas export TRITON_CACHE_DIR/tmp/triton_cache如果还是报错可以尝试降级 Triton 到 2.3.0pip install triton2.3.0我实测 2.3.0 在 CUDA 12.1 2080 Ti 上编译成功率最高。5.2 视觉 token 导致的显存尖峰多模态训练有个隐蔽的显存问题当 batch 里出现一张特别大的图时视觉 token 数量会突然飙升显存出现尖峰。即使你设了max_length2048如果图像分辨率没控制好实际序列长度可能超过这个值被截断但截断前的中间激活值已经占过显存了。解决办法是在数据预处理阶段就限制图像分辨率。Qwen3-VL 的 processor 有个max_pixels参数processor.image_processor.max_pixels 448 * 448 processor.image_processor.min_pixels 224 * 224把图像控制在 448x448 以内视觉 token 数量就可控了。代价是细节损失但对于大多数图文任务够用。5.3 loss 不下降先检查 dtype 一致性我遇到过训练 loss 一直卡在 2.3 不降的情况排查了半天发现是 dtype 不一致模型用 fp16 加载但 LoRA 适配器初始化时默认用了 fp32导致前向传播里出现 dtype 混用梯度计算出问题。解决方法是显式指定 LoRA 的 dtypemodel FastVisionModel.get_peft_model( ..., dtypetorch.float16, # 跟模型保持一致 )这个坑很隐蔽因为不报错只是效果差。判断方法是打印一下 LoRA 参数的 dtypefor name, param in model.named_parameters(): if lora in name: print(name, param.dtype) break如果输出torch.float32而模型是 fp16那就是这个问题。5.4 保存 checkpoint 时的显存翻倍训练中途保存 checkpoint 时如果用的是save_pretrained它会先把模型搬到 CPU 再保存这个过程中显存会短暂翻倍。2080 Ti 本来就紧张很容易在保存时 OOM。解决办法是用 unsloth 提供的保存方法它做了流式保存优化model.save_pretrained_merged( output_dir, processor, save_methodlora, # 只存 LoRA 适配器不合并 )只存 LoRA 适配器的话文件小、显存占用也低。等训练全部结束再合并成完整模型。6. 实测数据与效果对比6.1 显存占用对比我在同一份图文数据集上跑了三组对比都是 Qwen3-VL-7B、4bit 量化、max_length 2048、batch size 1配置峰值显存是否 OOMms-swift 原生12.8GB是ms-swift unsloth路径一9.6GB否ms-swift unsloth路径二10.4GB否可以看到光是把 unsloth 接进来显存就从 12.8GB 降到 9.6GB直接从不