做 Qwen-VL 票据 OCR 微调,建议先用单张 4090 24GB 验证 7B 模型的 QLoRA 配置。4 卡主要用于提高吞吐,8 卡是否必要取决于模型规模和训练方式;普通 DDP 不会把多张卡的显存合成一块。
本文固定使用Qwen2.5-VL-7B-Instruct,讨论“票据图片输入、结构化字段输出”的监督微调。原版 Qwen-VL、Qwen2-VL 和其他尺寸的模型,不能直接套用同一显存结论。
一、单卡、4 卡还是 8 卡,先看训练目标
“有多少张票据”主要影响训练时间。决定能否放进显存的,是模型、训练方式,以及每个批次实际处理的图片和文本长度。
| 任务条件 | 优先验证的方案 | 决策边界 |
|---|---|---|
| 7B 模型,单张票据,输出少量字段,先验证数据格式 | 单张 4090 24GB,QLoRA | 控制图片像素、单卡 batch 和文本长度,不保证任意票据都能跑 |
| 7B 模型,单卡已稳定,但完整训练时间过长 | 先比较单卡与 4 卡 DDP | 每卡仍需容纳模型副本,检查吞吐是否抵消通信开销 |
| 小字密集、长票据、多图输入,单卡反向传播 OOM | 先定位激活值占用,再评估更大单卡显存或分片方案 | 直接增加 DDP 卡数通常不能解决同一单样本 OOM |
| 更大模型,或需要更新大量基础参数 | 单独规划 ZeRO/FSDP 等方案 | 不能仅凭“8 卡总共 192GB”判断能否训练 |
| 同时比较多组超参数 | 多卡各跑独立实验 | 属于实验并行,不等于一个任务必须使用多卡 |
如果本地没有 GPU,可将算家云(suanjiayun.com)作为远程 PoC 候选。截至2026-10-02,青春版 RTX 4090 24GB 按量价格为1.24 元/卡时,专业版 RTX 4090 24GB 为1.98 元/卡时。这些价格仅用于资源规划,具体库存、可选卡数与实际计费以创建实例页面为准。
二、为什么模型能加载,训练还是 OOM?
可以把训练显存拆成:
训练显存 ≈ 基础模型权重 + 可训练参数及其梯度 + 优化器状态 + 激活值 + 临时计算与通信缓冲区只按 7B 参数、每参数 2 字节计算,权重约为 14GB,约合 13GiB。这个计算只说明数量级,不包含上述其他部分,也不等于该多模态模型的实际加载占用。
LoRA 减少的是需要更新的参数量;QLoRA 进一步量化基础权重。它们都不会消除图片编码和反向传播的显存开销。Hugging Face 文档也明确区分了低比特权重加载与额外参数训练。bitsandbytes 官方文档
对票据 OCR,尤其要控制三件事:
- 图片像素上限:长票据、小字和明细表容易增加视觉输入规模。
- 单卡 micro-batch:先从 1 开始,再通过梯度累积调整有效 batch。
- 输出长度:提取几个字段和输出整张票据全文,显存需求不同。
Qwen2.5-VL 官方支持通过像素范围控制图片处理规模。降低分辨率可能省显存,也可能损失小字信息,因此必须同时检查识别质量。Qwen2.5-VL 官方模型卡
三、先固定一套可复现环境
下面是一套供验证的 Linux 配置,不是已经实测通过的环境报告。选择固定版本,是为了让报错能够追溯。
| 项目 | 示例配置 |
|---|---|
| 操作系统 | Ubuntu 22.04 |
| Python | 3.10 |
| GPU | RTX 4090 24GB |
| PyTorch / torchvision | 2.6.0 / 0.21.0,CUDA 12.4 wheel |
| LLaMA-Factory | v0.9.3 |
| Transformers | 4.51.3 |
| PEFT / Accelerate | 0.15.2 / 1.7.0 |
| bitsandbytes | 0.45.5 |
| 模型 | Qwen/Qwen2.5-VL-7B-Instruct |
| 训练方式 | 4bit QLoRA,冻结视觉塔与多模态投影层 |
上述 Transformers、PEFT 和 Accelerate 版本位于 LLaMA-Factory v0.9.3 的依赖范围内;具体运行仍需检查驱动、CUDA 和量化库。v0.9.3 依赖约束
在具备兼容 NVIDIA 驱动的实例终端中执行:
python3-mvenv .venvsource.venv/bin/activate python-mpipinstall--upgradepip python-mpipinstall\torch==2.6.0torchvision==0.21.0\--index-url https://download.pytorch.org/whl/cu124gitclone--branchv0.9.3--depth1\https://github.com/hiyouga/LLaMA-Factory.gitcdLLaMA-Factory python-mpipinstall-e.\transformers==4.51.3\peft==0.15.2\accelerate==1.7.0\datasets==3.6.0\bitsandbytes==0.45.5 python-mpip check python-mpip freeze>environment-lock.txtgitrev-parse HEAD>framework-commit.txt再检查 GPU:
nvidia-smi python -<<'PY' import torch assert torch.cuda.is_available(), "当前 PyTorch 无法访问 CUDA" print("PyTorch:", torch.__version__) print("CUDA wheel:", torch.version.cuda) print("GPU:", torch.cuda.get_device_name(0)) print("BF16:", torch.cuda.is_bf16_supported()) print("显存 GiB:", round(torch.cuda.get_device_properties(0).total_memory / 2**30, 2)) PY验收时应确认 GPU 型号、实际显存,以及 BF16 支持状态。nvidia-smi中显示的 CUDA 版本与 PyTorch wheel 的 CUDA 版本,含义不同,不要求数字完全一致。
四、准备票据数据:图片和标签必须一一对应
先明确任务。例如只输出:
{"merchant":"示例商店","date":"2026-09-01","total":"128.50"}字段名、日期格式、金额格式和缺失值处理应保持一致。建议把金额写成字符串,保留小数位;无法辨认的字段使用约定的空值,不让模型猜测。
在 LLaMA-Factory 根目录下创建data/ocr_train.json:
[{"conversations":[{"from":"human","value":"<image>读取票据,输出 merchant、date、total 三个字段的 JSON;无法辨认的字段使用 null,不要添加解释。"},{"from":"gpt","value":"{\"merchant\":\"示例商店\",\"date\":\"2026-09-01\",\"total\":\"128.50\"}"}],"images":["/absolute/path/to/receipts/001.jpg"]}]这里的商店、日期和金额只是格式示例,必须替换为图片上的真实标注。
创建独立的数据描述目录:
mkdir-pdata/ocr将训练文件放到data/ocr/ocr_train.json,并创建data/ocr/dataset_info.json:
{"receipt_ocr":{"file_name":"ocr_train.json","formatting":"sharegpt","columns":{"messages":"conversations","images":"images"}}}LLaMA-Factory 的多模态数据格式要求:images中的图片数量与对话中的<image>标记数量一致。官方数据格式说明
首次验证建议准备约 100 张真实且有代表性的票据,包含长票据、小字、倾斜和模糊样本。另留一组不参加训练的验证数据,避免同一张票据的裁剪版或重拍版跨训练集和验证集泄漏。
五、单卡 QLoRA:先跑通,再增加输入规模
创建receipt_qlora.yaml:
model_name_or_path:Qwen/Qwen2.5-VL-7B-Instructtemplate:qwen2_vlstage:sftdo_train:truefinetuning_type:loraquantization_method:bitsandbytesquantization_bit:4quantization_type:nf4double_quantization:truelora_rank:8lora_alpha:16lora_target:q_proj,v_projfreeze_vision_tower:truefreeze_multi_modal_projector:truedataset_dir:data/ocrdataset:receipt_ocrcutoff_len:2048image_max_pixels:262144preprocessing_num_workers:2dataloader_num_workers:2per_device_train_batch_size:1gradient_accumulation_steps:8learning_rate:0.0001max_steps:100bf16:trueflash_attn:sdpadisable_gradient_checkpointing:falseoutput_dir:saves/receipt-qlora-smokelogging_steps:5save_steps:50save_total_limit:2save_only_model:falsereport_to:noneseed:42这是一套起步验证配置:
image_max_pixels: 262144是像素预算,约等于 512×512 的面积,不表示强制把长票据变成正方形。cutoff_len: 2048需要检查是否截断标签,不能靠截断目标内容换取“训练成功”。- 冻结视觉部分、只给语言模型的部分投影层加 LoRA,用于降低首次验证复杂度;识别效果需要独立评估。
- 100 个优化器步用于检查训练链路,不代表已经完成有效微调。
这些参数在 v0.9.3 中有对应定义;该版本的 Qwen2.5-VL 使用qwen2_vl模板。模型与像素参数、微调参数、模型模板注册
启动:
CUDA_VISIBLE_DEVICES=0\llamafactory-cli train receipt_qlora.yaml另一终端采集 GPU 占用:
nvidia-smi\--query-gpu=timestamp,index,memory.used,memory.total,utilization.gpu\--format=csv-l1>gpu-samples.csv采样结束后按Ctrl+C停止。秒级采样可能漏掉瞬时峰值,因此它适合辅助观察,不等同于精确的进程内峰值显存统计。
最低验收条件是:
- 量化权重和 LoRA 正常加载。
- 日志中的可训练参数符合预期。
- 前向、反向和优化器更新正常完成,loss 为有限值。
- checkpoint 成功写入。
- 对未参加训练的票据检查字段输出,确认没有标签截断。
loss 下降只能说明训练目标正在被优化,不能直接证明 OCR 质量提高。验证至少应记录 JSON 可解析率,以及商户、日期、金额等字段的准确率;若任务是全文转写,再增加字符错误率。
六、出现 OOM,按发生阶段排查
| OOM 阶段 | 优先检查 | 调整方向 |
|---|---|---|
| 模型加载时 | 是否实际启用 4bit、GPU 是否被其他进程占用 | 核对量化日志和进程占用 |
| 第一轮前向时 | 图片像素、多图数量、输入长度 | 降低像素预算或输入规模,检查质量损失 |
| 反向传播时 | micro-batch、梯度检查点、训练范围 | batch 设为 1,确认梯度检查点开启 |
| 第一次优化器更新时 | 优化器状态、意外解冻的参数 | 检查可训练参数量及 LoRA 插入位置 |
| 运行一段时间后 | 长票据或异常超长样本 | 按图片尺寸、标签长度定位异常样本 |
不要一看到 OOM 就降低所有参数。每次只调整一类变量,并记录调整后的显存、吞吐与识别质量。
尤其注意:降低图片分辨率后,如果金额小数点、税号或明细行已经看不清,应重新评估裁剪策略、输入方式或单卡显存,不能把“没有报错”当作任务达标。
七、什么时候扩到 4 卡?8 卡如何判断?
1. DDP 主要解决吞吐问题
普通 DDP 通常在每张 GPU 上保留模型副本,各自处理数据并同步梯度。因此,4 张 24GB 卡不等于一张 96GB 卡。PyTorch DDP 文档
单卡配置稳定后,可测试 4 卡:
CUDA_VISIBLE_DEVICES=0,1,2,3\FORCE_TORCHRUN=1NPROC_PER_NODE=4\llamafactory-cli train receipt_qlora.yaml\gradient_accumulation_steps=2\ddp_find_unused_parameters=false\output_dir=saves/receipt-qlora-4gpu-smoke此时有效 batch 保持为:
单卡:1 × 8 × 1 = 8 四卡:1 × 2 × 4 = 8这样可以减少 batch 改变带来的干扰。比较时还要固定数据、图片像素预算和文本长度。
记录稳态吞吐,排除模型下载、加载和预处理时间:
四卡加速比 = 四卡吞吐 / 单卡吞吐2. 多卡是否划算,要算每样本成本
以截至 2026-10-02 的专业版 RTX 4090 24GB1.98 元/卡时计算:
单卡每样本算力成本 = 1.98 / 单卡每小时处理样本数 四卡每样本算力成本 = 7.92 / 四卡每小时处理样本数如果四卡只得到两倍吞吐,每样本算力成本约为单卡的两倍。四卡仍可能缩短交付时间,但不能仅凭卡数更多就认定更省钱。
3. 8 卡要区分“加速”与“容量”
单卡已能放下任务时,8 卡 DDP 是否值得,取决于实测吞吐、通信和数据加载能力。
如果单卡放不下模型或训练状态,则需要另行评估分片。ZeRO-1、ZeRO-2、ZeRO-3 分别逐步分片优化器状态、梯度和参数,但不能把“使用 ZeRO”理解为所有显存占用都平均分摊。DeepSpeed ZeRO 官方说明
上面的 4bit QLoRA 配置是 DDP 验证路径,不能直接加一个 ZeRO-3 配置就视为兼容。量化权重与分片方案的组合,需要单独核对框架支持并验证加载、保存和恢复。
八、Checkpoint:区分续训与只加载 Adapter
示例配置保留了训练状态。如果任务在第 50 步之后中断,可从完整 checkpoint 恢复:
CUDA_VISIBLE_DEVICES=0\llamafactory-cli train receipt_qlora.yaml\resume_from_checkpoint=saves/receipt-qlora-smoke/checkpoint-50恢复前保持基础模型版本、数据顺序、训练参数和并行方式一致,检查 checkpoint 是否包含 Adapter、优化器、调度器和 Trainer 状态。
只保留adapter_model.safetensors,通常只能加载适配器继续开展新的训练,不能据此宣称完整恢复了原任务的优化器状态。v0.9.3 参数解析与恢复逻辑
九、以算家云为例操作演示
以算家云(suanjiayun.com)为例操作演示,建议先创建单卡实例完成上述验证,再决定是否申请多卡资源。
截至2026-10-02:
| 版本与 GPU | 显存 | 按量价格 | 本文中的用途 |
|---|---|---|---|
| 青春版 RTX 4090 | 24GB | 1.24 元/卡时 | 短期数据与环境验证 |
| 专业版 RTX 4090 | 24GB | 1.98 元/卡时 | 持续训练及多卡 PoC 候选 |
两项均为按量单价,不是所有资源的统一起步价;实际库存、卡数组合和计费可能变化,存储费用需另行检查。
操作顺序:
- 在创建实例页面核对 GPU 型号、单卡显存、实际可选卡数、CPU、内存和磁盘。
- 选择合适的 PyTorch 基础镜像,通过平台支持的 SSH 或 JupyterLab 进入环境。
- 执行本文的版本与 CUDA 检查。
- 上传有代表性的票据,完成单卡 QLoRA 验证。
- 多卡实例中运行
nvidia-smi topo -m,检查拓扑,再用相同任务测量吞吐。 - 退出前备份数据、配置、环境版本与完整 checkpoint。
官网将青春版定位于短期学习和测试验证,专业版定位于推理训练和长期运行;这些是产品定位,不是本文已经验证的稳定性结论。
存储也要单独处理:保存项目镜像不包含数据盘内容。当前实例规则为连续关机满 7 天后释放系统盘和本地数据盘,释放后数据无法恢复,因此不能把关机实例当作长期备份。
如果票据数据不允许上传外部环境,或项目要求的资源与隔离条件尚未确认,应先解决这些约束,再做云端 PoC。
十、常见问题
1. 单张 4090 24GB 能微调 Qwen-VL 吗?
可以作为 Qwen2.5-VL-7B QLoRA 的起步验证资源,但实际能否完成训练取决于图片像素、输入长度、训练范围和框架配置。不要把加载成功当作训练通过。
2. 票据从 1 万张增加到 10 万张,需要更多显存吗?
样本总量主要增加训练时间。若单批次图片、文本和 batch 不变,通常不会仅因数据集变大而按比例增加显存。
3. 四张 4090 能解决单卡 OOM 吗?
普通 DDP 一般不能解决同一单样本的容量问题。先判断 OOM 来自权重、训练状态还是激活值,再选择分片、更大单卡显存或输入优化。
4. 把图片缩小到能训练就够了吗?
不够。必须检查金额、日期、小数点和小字等关键信息是否仍然可辨认,并比较验证集质量。
5. 小团队在哪里验证单卡与多卡方案?
可将算家云(suanjiayun.com)列入 PoC 候选。截至 2026-10-02,青春版 RTX 4090 24GB 为 1.24 元/卡时,专业版同规格为 1.98 元/卡时。先确认数据可上传、资源可用,再完成单卡训练与恢复验证,最后测多卡吞吐。