ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

多卡微调大模型实战:deepspeed+trainer配置与避坑指南

多卡微调大模型实战:deepspeed+trainer配置与避坑指南 简介这份资源面向希望入门大模型多卡微调的人工智能开发者与研究者围绕Deepspeed与PyTorch Trainer的组合讲解如何以简洁代码实现多GPU并行微调覆盖垂直领域大模型与多模态场景适合具备一定PyTorch基础、想降低训练成本的学习者。压缩包共18个文件约116KB包含11个Python脚本、3个Shell启动脚本、2个JSON配置以及LICENSE和README脚本分别对应LoRA、P-Tuning、Freeze等微调方式与训练入口配置文件用于设定并行策略与训练参数结构紧凑便于按需取用。目前已有355人学习下载。通过其中的训练脚本、参数配置与数据加载模块读者可以理解Deepspeed与Trainer的集成方式掌握多卡环境下微调ChatGLM类模型的完整流程并借鉴不同微调策略的代码组织与排错思路为后续迁移到自有垂直领域任务提供可复用的工程模板。1. 多卡微调大模型为什么我最后选了 deepspeedtrainer 这套组合单卡 24G 显存想微调 7B 模型跑起来不是 OOM 就是 batch size 只能设成 1训练速度慢到怀疑人生。这是我带过好几个垂直领域大模型项目时反复遇到的场景。后来我把训练框架换成了 deepspeed 配合 HuggingFace trainer多卡并行、显存优化、梯度累积这些事基本不用自己手写配置文件改几行就能跑起来。这份资源包做的就是这件事用最少的代码改动把单卡跑不动的微调任务搬到多卡上同时把显存占用压下来。它适合已经跑通过单卡微调、想往多卡扩展的从业者也适合刚入门大模型微调、想直接上手一套能跑通的多卡方案的人。核心不是教你从零写训练循环而是给你一套经过验证的配置模板和启动脚本改改路径和参数就能用。2. deepspeed 与 trainer 的协作机制谁管显存谁管调度2.1 两者分工的底层逻辑很多人第一次接触 deepspeedtrainer 会搞混一件事到底谁在管多卡通信谁在管训练流程。简单说trainer 负责的是训练循环本身——前向、反向、优化器更新、日志、checkpoint 保存这些。deepspeed 负责的是把这些操作切分到多张卡上并且用 ZeRO 系列策略把显存占用降下来。ZeRO 的核心思路是把模型状态参数、梯度、优化器状态分片存储。Stage 1 只分片优化器状态Stage 2 加上梯度分片Stage 3 连参数也分片。Stage 3 最省显存但通信开销最大。我一般会根据模型大小和卡数来选7B 模型用 2 到 4 张 24G 卡Stage 2 通常够用13B 以上或者卡数多但单卡显存小就上 Stage 3。trainer 这边通过deepspeed参数接收一个配置文件路径然后在内部把训练循环的每一步都交给 deepspeed 的 engine 来执行。你不需要改模型代码也不需要手动写DistributedDataParalleltrainer 会自动处理。2.2 配置文件的关键字段拆解deepspeed 的配置文件是 JSON 格式下面是一个我常用的 Stage 2 配置模板{ train_batch_size: auto, train_micro_batch_size_per_gpu: auto, gradient_accumulation_steps: auto, optimizer: { type: AdamW, params: { lr: auto, betas: auto, eps: auto, weight_decay: auto } }, scheduler: { type: WarmupDecayLR, params: { warmup_min_lr: auto, warmup_max_lr: auto, warmup_num_steps: auto, total_num_steps: auto } }, zero_optimization: { stage: 2, offload_optimizer: { device: cpu, pin_memory: true }, allgather_partitions: true, allgather_bucket_size: 2e8, overlap_comm: true, reduce_scatter: true, reduce_bucket_size: 2e8, contiguous_gradients: true }, fp16: { enabled: true, loss_scale: 0, loss_scale_window: 1000, initial_scale_power: 16, hysteresis: 2, min_loss_scale: 1 }, gradient_clipping: 1.0, steps_per_print: 100, wall_clock_breakdown: false }train_batch_size设成auto时trainer 会根据你的per_device_train_batch_size、gradient_accumulation_steps和卡数自动算。offload_optimizer把优化器状态放到 CPU 内存显存不够时这是救命选项但会拖慢速度我一般只在显存实在压不下来时才开。overlap_comm让通信和计算重叠能提一点速度建议开着。fp16部分loss_scale设 0 表示用动态 loss scalinginitial_scale_power是初始缩放因子16 对应 65536一般不用改。2.3 启动脚本与多卡通信初始化配置文件写好后启动方式有两种用deepspeed命令直接启动或者用torchrun配合 trainer 的deepspeed参数。我习惯用后者因为 trainer 对多卡环境的处理更省心。# 用 torchrun 启动指定 4 张卡 torchrun --nproc_per_node4 \ --master_port29500 \ train.py \ --deepspeed ds_config.json \ --model_name_or_path /path/to/your/model \ --output_dir ./output \ --num_train_epochs 3 \ --per_device_train_batch_size 4 \ --gradient_accumulation_steps 4 \ --learning_rate 2e-5 \ --fp16 True \ --logging_steps 10 \ --save_steps 500nproc_per_node就是卡数master_port随便选一个不冲突的端口。per_device_train_batch_size是每张卡上的 batch sizegradient_accumulation_steps是梯度累积步数两者相乘再乘以卡数就是全局 batch size。比如这里 4×4×464。学习率 2e-5 是微调 7B 模型的常见起点具体要看你的数据量和任务类型。注意torchrun启动时如果卡数超过 8建议设置NCCL_IB_DISABLE1和NCCL_P2P_DISABLE1来避免一些通信库的兼容问题尤其是在混合显卡型号的机器上。3. 从单卡到多卡代码改造与数据准备的实际操作3.1 trainer 代码的最小改动清单如果你已经有一份单卡微调的 trainer 代码改成多卡其实只需要动几个地方。下面是一个完整的训练脚本骨架import torch from transformers import ( AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments, DataCollatorForSeq2Seq ) from datasets import load_dataset # 1. 加载模型和 tokenizer model AutoModelForCausalLM.from_pretrained( Qwen/Qwen2.5-7B, torch_dtypetorch.float16, device_mapNone # 多卡时不要设 device_map让 deepspeed 管 ) tokenizer AutoTokenizer.from_pretrained(Qwen/Qwen2.5-7B) tokenizer.pad_token tokenizer.eos_token # 2. 加载数据集并 tokenize dataset load_dataset(json, data_filestrain.json, splittrain) def tokenize_fn(example): tokens tokenizer( example[text], truncationTrue, max_length1024, paddingFalse ) tokens[labels] tokens[input_ids].copy() return tokens tokenized dataset.map(tokenize_fn, remove_columnsdataset.column_names) # 3. 训练参数 training_args TrainingArguments( output_dir./output, num_train_epochs3, per_device_train_batch_size4, gradient_accumulation_steps4, learning_rate2e-5, fp16True, logging_steps10, save_steps500, save_total_limit3, deepspeedds_config.json, # 关键指向 deepspeed 配置 report_tonone ) # 4. 初始化 trainer trainer Trainer( modelmodel, argstraining_args, train_datasettokenized, data_collatorDataCollatorForSeq2Seq(tokenizer, paddingTrue) ) # 5. 开始训练 trainer.train()关键改动就三处device_map设成NoneTrainingArguments里加deepspeed参数其余交给 trainer 自动处理。DataCollatorForSeq2Seq负责把不同长度的序列 padding 到同一长度多卡时每个进程会拿到自己的数据分片。3.2 数据格式与 tokenize 的边界处理数据格式我一般用 JSON Lines每行一个样本字段名和tokenize_fn里的对应。比如{text: ### 指令解释什么是梯度累积\n### 回答梯度累积是在反向传播时...} {text: ### 指令多卡训练时如何设置学习率\n### 回答一般按全局 batch size 线性缩放...}max_length设 1024 是保守值如果你的数据平均长度只有 256可以降到 512 省显存。paddingFalse在 tokenize 阶段不 padding交给 data collator 动态处理这样能减少显存浪费。labels直接复制input_ids这是自回归语言模型的标准做法如果你要做指令微调且只计算回答部分的 loss需要把指令部分的 label 设成 -100。注意多卡训练时每个进程都会独立加载一份数据集。如果数据集很大建议用datasets库的load_from_disk先缓存到本地避免每个进程重复读盘。3.3 显存与吞吐的实测调参以 4 张 24G 卡微调 7B 模型为例不同配置下的显存占用和吞吐大致如下配置单卡显存全局 batch size每秒样本数Stage 2, bs2, ga4约 18G32约 12Stage 2, bs4, ga4约 22G64约 18Stage 3, bs4, ga4约 14G64约 10Stage 2 offload, bs4, ga4约 10G64约 6Stage 3 显存降得多但速度掉得也明显offload 更狠。我的经验是先试 Stage 2 加最大 batch size如果 OOM 再降 batch size 或上 Stage 3。gradient_accumulation_steps用来补全局 batch size不占额外显存但会增加训练时间。4. 避坑与排查多卡微调里最容易翻车的五个地方4.1 现象训练启动后卡在初始化日志停在 “Initializing deepspeed”原因通常是 NCCL 通信没建起来。多卡机器上如果网卡配置不对或者master_port被占用就会卡在这一步。解决方法是先检查master_port是否空闲用netstat -tlnp | grep 29500看一眼。如果端口没问题试试设置export NCCL_DEBUGINFO看详细日志常见的是网卡选错了可以用export NCCL_SOCKET_IFNAMEeth0指定正确的网卡。4.2 现象loss 变成 NaN 或者突然飙升多卡训练时 loss 比单卡更容易炸原因一般是学习率没随全局 batch size 调整。单卡 batch size 是 4学习率 2e-5多卡全局 batch size 变成 64学习率还保持 2e-5 就可能偏大。常见做法是按线性缩放全局 batch size 翻倍学习率也翻倍但上限一般不超过 5e-5。另外检查gradient_clipping是否设了我一般设 1.0。4.3 现象保存的 checkpoint 加载时报错提示参数形状不匹配这是 ZeRO Stage 3 的典型问题。Stage 3 把参数分片存储保存下来的 checkpoint 是分片格式直接用from_pretrained加载会失败。解决办法是用 deepspeed 提供的zero_to_fp32.py脚本把分片合并成完整模型python zero_to_fp32.py ./output/checkpoint-500 ./output/merged_model合并后再用from_pretrained加载就没问题了。如果用的是 Stage 2checkpoint 本身就是完整的不需要这一步。4.4 现象多卡训练速度反而比单卡慢卡数增加但吞吐没上去甚至下降通常是通信开销吃掉了计算收益。检查overlap_comm是否开启allgather_bucket_size和reduce_bucket_size是否设得太大。这两个 bucket size 默认 5e8我一般降到 2e8减少通信等待。另外如果卡之间是 PCIe 而不是 NVLink通信带宽有限卡数超过 4 张后收益递减很明显。4.5 现象训练中途 OOM但显存监控显示还有余量这是显存碎片化导致的。PyTorch 的缓存分配器在长时间训练后会产生碎片明明总显存够但连续大块不够。解决办法是设置PYTORCH_CUDA_ALLOC_CONFmax_split_size_mb:128限制单次分配的最大块大小减少碎片。另外contiguous_gradients设成true也有帮助。5. 进阶技巧用 Stage 3 offload 在 2 张 24G 卡上微调 13B 模型5.1 配置调整与显存账本13B 模型用 fp16 存储光参数就占 26G单卡 24G 根本放不下。2 张卡用 Stage 3 分片后每卡约 13G 参数加上梯度、优化器状态和激活值还是紧张。这时候需要把优化器状态 offload 到 CPU{ zero_optimization: { stage: 3, offload_optimizer: { device: cpu, pin_memory: true }, offload_param: { device: cpu, pin_memory: true }, stage3_prefetch_bucket_size: 5e7, stage3_param_persistence_threshold: 1e5, stage3_max_live_parameters: 1e9, stage3_max_reuse_distance: 1e9, stage3_gather_16bit_weights_on_model_save: true } }offload_param把参数也放到 CPU显存占用能压到 10G 以下但速度会掉到单卡的几分之一。stage3_gather_16bit_weights_on_model_save设成true后保存 checkpoint 时会自动合并参数省去手动跑zero_to_fp32.py的步骤。5.2 训练速度与显存的实际权衡我用 2 张 24G 卡微调 13B 模型Stage 3 加 offload 后单卡显存约 9G每秒处理约 3 个样本。同样的任务如果用 4 张 40G 卡跑 Stage 2每秒能到 15 个样本。所以 offload 是显存不够时的后悔药不是常规方案。如果预算允许优先加卡或者换大显存卡比 offload 划算得多。5.3 验证 checkpoint 是否正确的习惯从那以后我每次训练完都强制走一遍验证流程先用zero_to_fp32.py合并参数如果没开自动合并然后用from_pretrained加载合并后的模型跑一条推理看输出是否正常。这一步能提前发现参数分片没合并、权重形状不对、tokenizer 不匹配这些问题。具体命令# 合并参数 python zero_to_fp32.py ./output/checkpoint-1000 ./output/merged # 快速推理验证 python -c from transformers import AutoModelForCausalLM, AutoTokenizer model AutoModelForCausalLM.from_pretrained(./output/merged, torch_dtypeauto) tokenizer AutoTokenizer.from_pretrained(./output/merged) inputs tokenizer(### 指令什么是 deepspeed\n### 回答, return_tensorspt) outputs model.generate(**inputs, max_new_tokens100) print(tokenizer.decode(outputs[0], skip_special_tokensTrue)) 如果输出是乱码或者重复大概率是合并步骤出了问题。这个习惯帮我省过好几次重新训练的功夫。希望帮到你。本文还有配套的精品资源点击获取
返回列表