外观
116.基于魔搭平台进行DDP分布式训练
来源:语雀原文 | 整理日期:2026-09-22
示例代码
数据并行
数据并行,就是将数据集分为 N 份,分别装载到 N 个 GPU 节点中,每个 GPU 节点持有一个完整的模型副本,分别基于每个 GPU 中的数据去进行梯度求导。在 GPU0 上对每个 GPU 中的梯度进行累加,最后,再将 GPU0 聚合后的结果广播到其他 GPU 节点。数据并行不仅仅指对训练的数据并行操作,还可以对网络模型梯度、权重参数、优化器状态等数据进行并行。
数据并行
数据并行 (torch.nn.DataParallel),基于单进程多线程进行实现的,使用一个进程来计算模型权重,在每个批处理期间将数据分发到每个 GPU。
DataParallel 的计算过程如下所示:
- 将 inputs 从主 GPU 分发到所有 GPU 上。
- 将 model 从主 GPU 分发到所有 GPU 上。
- 每个 GPU 分别独立进行前向传播,得到 outputs。
- 将每个 GPU 的 outputs 发回主 GPU。
- 在主 GPU 上,通过 loss function 计算出 loss,对 loss function 求导,求出损失梯度。
- 计算得到的梯度分发到所有 GPU 上。
- 反向传播计算参数梯度。
- 将所有梯度回传到主 GPU,通过梯度更新模型权重。
- 不断重复上面的过程。
分布式数据并行
PyTorch 官方建议使用 DistributedDataParallel,而不是 DataParallel 类来进行多 GPU 训练,即使在单机多卡的情况下。那么下面我们来看看 PyTorch DDP。
分布式数据并行 (torch.nn.DistributedDataParallel),基于多进程进行实现的,每个进程都有独立的优化器,执行自己的更新过程。每个进程都执行相同的任务,并且每个进程都与所有其他进程通信。进程(GPU)之间只传递梯度,这样网络通信就不再是瓶颈。DistributedDataParallel 方式可以更好地进行多机多卡运算,更好的进行负载均衡,运行效率也更高,虽然使用起来较为麻烦,但对于追求性能来讲是一个更好的选择。
具体流程如下:
- 首先将 rank=0 进程中的模型参数广播到进程组中的其他进程;
- 然后,每个 DDP 进程都会创建一个 local Reducer 来负责梯度同步。
- 在训练过程中,每个进程从磁盘加载 batch 数据,并将它们传递到其 GPU。每个 GPU 都有自己的前向过程,完成前向传播后,梯度在各个 GPUs 间进行 All-Reduce,每个 GPU 都收到其他 GPU 的梯度,从而可以独自进行反向传播和参数更新。
- 同时,每一层的梯度不依赖于前一层,所以梯度的 All-Reduce 和后向过程同时计算,以进一步缓解网络瓶颈。
- 在后向过程的最后,每个节点都得到了平均梯度,这样各个 GPU 中的模型参数保持同步 。
环境准备
推荐的第三方库版本参考SWIFT安装文档
plain
pip install ms-swift -U
# 若使用deepspeed zero2/zero3
pip install deepspeed -U微调训练
ms-swift使用了分层式的设计思想,用户可以使用命令行界面、Web-UI界面和直接使用Python的方式进行微调。
使用CLI
我们提供了10分钟在单卡3090上对Qwen2.5-7B-Instruct进行自我认知微调的最佳实践,具体参考这里,这可以帮助您快速了解SWIFT。
此外,我们给出了一系列脚本帮助您了解SWIFT的训练能力:
轻量化训练:SWIFT支持的轻量微调示例可以参考这里。(注意:这些方式预训练也可以使用,但预训练通常使用全参数训练)。
分布式训练:SWIFT支持的分布式训练技术包括:DDP、device_map、DeepSpeed ZeRO2/ZeRO3、FSDP。
device_map: 简易模型并行。如果存在多GPU,device_map会自动开启。这会将模型按层均匀的划分到可见的GPU中,显著降低显存消耗,但是训练速度通常会降低,因为是串行的。
DDP+device_map:将按组对模型进行device_map划分,参考这里。
小帖士:
- 在使用
swift sft通过LoRA技术微调base模型为chat模型时,有时需要手动设置模板。通过添加--template default参数来避免base模型因未见过对话模板中的特殊字符而无法正常停止的情况。具体参考这里。 - 如果需要在断网环境下进行训练,请设置
--model <model_dir>和--check_model false。如果对应的模型需要git clonegithub的仓库,例如deepseek-ai/Janus-Pro-7B,请设置手动下载仓库,并设置--local_repo_path <repo_dir>。具体参数含义请参考命令行参数文档。 - SWIFT默认在训练时设置
--gradient_checkpointing true来节约显存,这会略微降低训练速度。 - 若使用DDP进行训练,出现报错:
RuntimeError: Expected to mark a variable ready only once.,请额外设置参数--gradient_checkpointing_kwargs '{"use_reentrant": false}'或者使用DeepSpeed进行训练。
ddp 微调
bash
# 27.5GiB * 2
nproc_per_node=1
CUDA_VISIBLE_DEVICES=0,1 \
NPROC_PER_NODE=$nproc_per_node \
swift sft \
--model Qwen/Qwen2-1.5B \
--train_type lora \
--torch_dtype bfloat16 \
--dataset 'swift/self-cognition#10' \
--num_train_epochs 1 \
--per_device_train_batch_size 1 \
--per_device_eval_batch_size 1 \
--learning_rate 1e-4 \
--lora_rank 8 \
--lora_alpha 32 \
--target_modules all-linear \
--gradient_accumulation_steps $(expr 16 / $nproc_per_node) \
--eval_steps 100 \
--save_steps 100 \
--save_total_limit 2 \
--logging_steps 5 \
--max_length 2048 \
--output_dir output \
--system 'You are a helpful assistant.' \
--warmup_ratio 0.05 \
--dataloader_num_workers 4 \
--model_author swift \
--model_name swift-robot \
--gradient_checkpointing_kwargs '{"use_reentrant": false}'ddp device_map微调
bash
# 27.5GiB * 2
nproc_per_node=1
CUDA_VISIBLE_DEVICES=0,1 \
NPROC_PER_NODE=$nproc_per_node \
swift sft \
--model Qwen/Qwen2-1.5B \
--train_type lora \
--torch_dtype bfloat16 \
--dataset 'swift/self-cognition#10' \
--num_train_epochs 1 \
--per_device_train_batch_size 1 \
--per_device_eval_batch_size 1 \
--learning_rate 1e-4 \
--lora_rank 8 \
--lora_alpha 32 \
--target_modules all-linear \
--gradient_accumulation_steps $(expr 16 / $nproc_per_node) \
--eval_steps 100 \
--save_steps 100 \
--save_total_limit 2 \
--logging_steps 5 \
--max_length 2048 \
--output_dir output \
--system 'You are a helpful assistant.' \
--warmup_ratio 0.05 \
--dataloader_num_workers 4 \
--model_author swift \
--model_name swift-robot \
--gradient_checkpointing_kwargs '{"use_reentrant": false}'debug微调
你可以使用以下方式进行debug,这与使用命令行微调是等价的,但此方式不支持分布式。微调命令行运行入口可以查看这里。
plain
from swift.llm import sft_main, TrainArguments
result = sft_main(TrainArguments(
model='Qwen/Qwen2.5-7B-Instruct',
train_type='lora',
dataset=['AI-ModelScope/alpaca-gpt4-data-zh#500',
'AI-ModelScope/alpaca-gpt4-data-en#500',
'swift/self-cognition#500'],
torch_dtype='bfloat16'
))
print(result)模型合并
bash
# Since `output/vx-xxx/checkpoint-xxx` is trained by swift and contains an `args.json` file,
# there is no need to explicitly set `--model`, `--system`, etc., as they will be automatically read.
swift export \
--adapters output/vx-xxx/checkpoint-xxx \
--merge_lora true模型推理
使用CLI对LoRA训练的checkpoint进行推理:
plain
CUDA_VISIBLE_DEVICES=0 \
swift infer \
--adapters output/vx-xxx/checkpoint-xxx \
--infer_backend pt \
--stream true \
--temperature 0 \
--max_new_tokens 2048- adapters文件夹中包含了训练的参数文件
args.json,因此不需要额外指定--model,--system,swift会自动读取这些参数。如果要关闭此行为,可以设置--load_args false。 - 如果使用全参数训练,请使用
--model替代--adapters指定训练的checkpoint目录。更多参考。 - 你可以使用
swift app替代swift infer进行界面推理。 - 你可以选择对LoRA进行merge(额外指定
--merge_lora true),然后指定--infer_backend vllm/lmdeploy进行推理加速。
对数据集中的验证集进行批量推理:
plain
CUDA_VISIBLE_DEVICES=0 \
swift infer \
--adapters output/vx-xxx/checkpoint-xxx \
--infer_backend pt \
--temperature 0 \
--max_new_tokens 2048 \
--load_data_args true \
--max_batch_size 1- 你可以设置
--max_batch_size 8,从而使用--infer_backend pt进行批量处理。若使用infer_backend vllm/lmdeploy则无需指定,会进行自动batch。 --load_data_args true会额外读取训练存储参数文件args.json中的数据参数。
若想对额外的测试集进行推理,而不使用训练时的验证集,使用--val_dataset <dataset_path>进行推理:
plain
CUDA_VISIBLE_DEVICES=0 \
swift infer \
--adapters output/vx-xxx/checkpoint-xxx \
--infer_backend pt \
--temperature 0 \
--max_new_tokens 2048 \
--val_dataset <dataset-path> \
--max_batch_size 1使用Python对训练后LoRA推理的例子如下:
plain
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
from swift.llm import (
PtEngine, RequestConfig, safe_snapshot_download, get_model_tokenizer, get_template, InferRequest
)
from swift.tuners import Swift
# 请调整下面几行
model = 'Qwen/Qwen2.5-7B-Instruct'
lora_checkpoint = safe_snapshot_download('swift/test_lora') # 修改成checkpoint_dir
template_type = None # None: 使用对应模型默认的template_type
default_system = "You are a helpful assistant." # None: 使用对应模型默认的default_system
# 加载模型和对话模板
model, tokenizer = get_model_tokenizer(model)
model = Swift.from_pretrained(model, lora_checkpoint)
template_type = template_type or model.model_meta.template
template = get_template(template_type, tokenizer, default_system=default_system)
engine = PtEngine.from_model_template(model, template, max_batch_size=2)
request_config = RequestConfig(max_tokens=512, temperature=0)
# 这里使用了2个infer_request来展示batch推理
infer_requests = [
InferRequest(messages=[{'role': 'user', 'content': 'who are you?'}]),
InferRequest(messages=[{'role': 'user', 'content': '浙江的省会在哪?'},
{'role': 'assistant', 'content': '浙江的省会在哪?'},
{'role': 'user', 'content': '这里有什么好吃的'},]),
]
resp_list = engine.infer(infer_requests, request_config)
query0 = infer_requests[0].messages[0]['content']
print(f'response0: {resp_list[0].choices[0].message.content}')
print(f'response1: {resp_list[1].choices[0].message.content}')