Accelerate 是由 Hugging Face 开发的一个轻量级 Python 库,旨在让 PyTorch 的分布式训练变得极其简单

Accelerate 是由 Hugging Face 开发的一个轻量级 Python 库,旨在让 PyTorch 的分布式训练变得极其简单

对比:手动 DDP (Trainer + 4bit 量化)

import os import torch from transformers import ( AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer, DataCollatorForLanguageModeling ) from transformers import BitsAndBytesConfig from peft import LoraConfig, get_peft_model # ========================================== # 第一部分:手动环境初始化 (手动 DDP 的标志) # ========================================== # 1. 显式获取当前进程的排名 # 当你使用 torchrun --nproc_per_node=2 启动时, # 进程 0 的 LOCAL_RANK 为 0,进程 1 的 LOCAL_RANK 为 1 local_rank = int(os.environ.get("LOCAL_RANK", 0)) # 2. 显式设置当前进程使用的 GPU # 这一步至关重要,防止所有进程都默认去操作 cuda:0 torch.cuda.set_device(local_rank) # ========================================== # 第二部分:模型加载 (关键:手动 device_map) # ========================================== model_name = "/path/to/your/model" bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_compute_dtype=torch.float16, bnb_4bit_quant_type="nf4", bnb_4bit_use_double_quant=True ) # 3. 加载模型时手动指定 device_map # {"": local_rank} 的意思是:把整个模型完整地放到 local_rank 这张卡上 # 如果不写这一行,accelerate 可能会尝试把模型切分到多张卡 (模型并行),破坏 DDP model = AutoModelForCausalLM.from_pretrained( model_name, quantization_config=bnb_config, device_map={"": local_rank}, # <--- 核心手动配置 torch_dtype=torch.float16 ) tokenizer = AutoTokenizer.from_pretrained(model_name) # ... (数据集处理代码略) ... # 配置 LoRA lora_config = LoraConfig( r=16, lora_alpha=32, target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], lora_dropout=0.05, bias="none", task_type="CAUSAL_LM" ) model = get_peft_model(model, lora_config) model.enable_input_require_grads() # 量化训练必须开启 # ========================================== # 第三部分:Trainer 参数配置 (显式开启 DDP 优化) # ========================================== training_args = TrainingArguments( output_dir="./output", num_train_epochs=3, per_device_train_batch_size=1, gradient_accumulation_steps=16, # --- 以下是 DDP 相关的关键手动配置 --- # 1. 指定通信后端 (通常为 nccl) ddp_backend="nccl", # 2. LoRA 训练建议设为 False,提升效率 # 如果为 True,DDP 会反向遍历图找没用到的参数,非常慢 ddp_find_unused_parameters=False, # 3. 梯度检查点配置 (非重入式,避免与 DDP 冲突) gradient_checkpointing_kwargs={"use_reentrant": False}, gradient_checkpointing=True, # 4. 混合精度 fp16=True, # 其他常规参数 logging_steps=10, save_steps=100, save_total_limit=2, report_to="none", remove_unused_columns=False ) trainer = Trainer( model=model, args=training_args, train_dataset=train_dataset, # 假设已定义 data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False) ) # 开始训练 trainer.train() 
为什么这种写法叫“手动 DDP”?

通常使用 Trainer 时,你不需要写前两行代码(local_rank 和 set_device),只需要在 TrainingArguments 里什么都不写,Trainer 会自动检测是否是多卡环境并自动分配。

但在这个特定场景下,手动接管了分配权,原因如下:量化导致的复杂度BitsAndBytesConfig (4bit 加载) 依赖 accelerate 库的 device_map 机制。避免模型并行:如果不手动指定 device_map={"": local_rank}accelerate 可能会认为你的模型很大,尝试把它拆分到 GPU 0 和 GPU 1 上(模型并行)。强制数据并行:你想做的是 DDP(数据并行),即 GPU 0 有一份完整模型,GPU 1 也有一份完整模型。所以你必须手动告诉代码:“不管模型多大,请把它完整地塞到 local_rank 这张卡里,不要拆分。”总结

这种写法是 Trainer (高级封装) 和 PyTorch DDP (底层逻辑) 的一种混合体。它既保留了 Trainer 的易用性,又解决了量化模型在多卡环境下的设备分配冲突问题。

Accelerate 是 Hugging Face 提供的一个轻量级库,用于简化 单机多卡(或分布式)训练的代码编写。它底层封装了 PyTorch 的 DistributedDataParallel(DDP),让你无需手动处理进程启动、设备分配、梯度同步等细节。

1. 核心特点

  • 无需手动管理设备:不需要写 torch.cuda.set_device(local_rank)
  • 自动封装模型:不需要手动 DistributedDataParallel(model)
  • 自动处理梯度同步:直接用 accelerator.backward(loss) 替代 loss.backward()
from accelerate import Accelerator # Step 1: 初始化 accelerator = Accelerator() # Step 2: 定义你的 model, optimizer, dataloader, loss model = ... optimizer = ... dataloader = ... loss_fn = ... # Step 3: 用 accelerator.prepare() 包装所有组件 model, optimizer, dataloader, loss_fn = accelerator.prepare( model, optimizer, dataloader, loss_fn ) # Step 4: 训练循环(注意 backward 要用 accelerator) for batch in dataloader: optimizer.zero_grad() outputs = model(batch) loss = loss_fn(outputs, labels) accelerator.backward(loss) # ← 关键!不是 loss.backward() optimizer.step() # 启动训练 # 单机多卡(自动检测 GPU 数量) accelerate launch train.py # 指定 4 卡 accelerate launch --num_processes=4 train.py # CPU / MPS / TPU 也支持 accelerate launch --cpu train.py

下面是一个完整的 使用 accelerate 实现单机多卡 DDP 训练 的示例,包含:

  • 数据加载
  • 模型定义
  • 优化器与训练循环
  • 自动设备放置与梯度同步

前提条件

  • 安装:pip install accelerate torch torchvision
  • 环境:单台机器,配备 ≥2 张 GPU(如 2x A100 / RTX 3090)
  • 启动方式:直接运行 Python 脚本accelerate 会自动处理 torch.distributed 初始化)

示例代码:图像分类训练(ResNet + CIFAR-10)

# train_ddp_accelerate.py from accelerate import Accelerator from torch.utils.data import DataLoader from torchvision import datasets, transforms, models import torch import torch.nn as nn import torch.optim as optim def main(): # 1. 初始化 Accelerator(自动处理 DDP、设备、混合精度等) accelerator = Accelerator() # 2. 准备数据 transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ]) train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) # 注意:Accelerator 会自动对 sampler 进行分布式处理 train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4) # 3. 模型、优化器、损失函数 model = models.resnet18(num_classes=10) optimizer = optim.Adam(model.parameters(), lr=1e-3) criterion = nn.CrossEntropyLoss() # 4. 使用 accelerator.prepare() 包装所有组件 model, optimizer, train_loader, criterion = accelerator.prepare( model, optimizer, train_loader, criterion ) # 5. 训练循环 model.train() for epoch in range(10): for batch_idx, (data, target) in enumerate(train_loader): optimizer.zero_grad() output = model(data) loss = criterion(output, target) # 关键:用 accelerator.backward() 替代 loss.backward() accelerator.backward(loss) optimizer.step() if batch_idx % 100 == 0 and accelerator.is_main_process: print(f"Epoch {epoch}, Batch {batch_idx}, Loss: {loss.item():.4f}") if __name__ == "__main__": main()

如何运行?

方法一:直接运行(推荐)
# 单机多卡(例如 2 卡) accelerate launch --multi_gpu train_ddp_accelerate.py
accelerate launch 会自动设置 RANK, WORLD_SIZE, MASTER_ADDR 等环境变量,并启动多个进程。
方法二:指定 GPU 数量(可选)
accelerate launch --num_processes=2 --multi_gpu train_ddp_accelerate.py

accelerator.prepare() 做了什么?

组件处理效果
model自动包装为 DistributedDataParallel(DDP)
optimizer适配 DDP 下的参数引用
DataLoader自动插入 DistributedSampler,确保每个 GPU 加载不同数据子集
loss通常不需要 prepare,但可参与自动设备迁移

高级功能(按需启用)

1. 混合精度训练(FP16)
accelerator = Accelerator(mixed_precision="fp16") # 或 "bf16"
2. 梯度累积(模拟大 batch)
accelerator = Accelerator(gradient_accumulation_steps=4) # 在 backward 前加: if (step + 1) % 4 == 0: optimizer.zero_grad() accelerator = Accelerator(gradient_accumulation_steps=8) for step, batch in enumerate(dataloader): with accelerator.accumulate(model): # 自动处理 zero_grad / step / sync loss = model(batch) accelerator.backward(loss) optimizer.step() # scheduler.step() 也可放这里
3. 保存/加载模型(仅主进程保存)
if accelerator.is_main_process: unwrapped_model = accelerator.unwrap_model(model) torch.save(unwrapped_model.state_dict(), "model.pth")
4. 日志只在主进程打印
if accelerator.is_main_process: print("Only printed once!")

注意事项

  1. 不要手动调用 model.to(device) —— accelerator.prepare() 会自动处理。
  2. 不要用 loss.backward() —— 必须用 accelerator.backward(loss)
  3. 数据集无需手动划分 —— DistributedSampler 已自动插入。
  4. 验证/测试时也要用 accelerator.prepare() 包装 DataLoader。

优势总结

传统 DDP使用 Accelerate
需写 init_process_groupDistributedSamplermodel = DDP(model)一行 Accelerator() 全搞定
多进程启动需 torchrun 或 mp.spawn直接 accelerate launch
混合精度、梯度累积代码复杂参数化配置即可
保存模型需处理 module. 前缀unwrap_model() 自动处理

总结

accelerate = PyTorch 分布式训练的“胶水层”
它不改变你的训练逻辑,只帮你自动处理设备、并行、精度、同步等底层细节,让你专注模型和算法本身。

=========================================================================

完整代码示例

假设要微调一个 BERT 模型做文本分类:

import torch from torch.utils.data import DataLoader from transformers import AutoTokenizer, AutoModelForSequenceClassification, get_linear_schedule_with_warmup from datasets import load_dataset from accelerate import Accelerator from tqdm.auto import tqdm # 1. 初始化 Accelerator (这是核心!) # fp16/bf16: 开启混合精度 # gradient_accumulation_steps: 梯度累积 accelerator = Accelerator( mixed_precision="fp16", # 或者 "bf16" gradient_accumulation_steps=4 ) # 2. 加载模型和数据 (注意:这里不需要手动 .to(device)) model_name = "bert-base-chinese" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2) # 准备数据 dataset = load_dataset("csv", data_files="your_data.csv", split="train") def preprocess_function(examples): return tokenizer(examples["text"], truncation=True,, max_length=128) tokenized_dataset = dataset.map(preprocess_function, batched=True) tokenized_dataset = tokenized_dataset.remove_columns(["text"]) tokenized_dataset.set_format("torch") dataloader = DataLoader(tokenized_dataset, shuffle=True, batch_size=16) # 优化器 optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5) # 3. 关键步骤:使用 prepare 将模型、优化器、数据加载器包装起来 # 这一步会自动: # - 将模型放到对应的 GPU # - 用 DDP 包装模型 # - 将数据分发到对应的 GPU model, optimizer, dataloader = accelerator.prepare(model, optimizer, dataloader) # 训练循环 num_epochs = 3 num_training_steps = num_epochs * len(dataloader) lr_scheduler = get_linear_schedule_with_warmup( optimizer=optimizer, num_warmup_steps=0, num_training_steps=num_training_steps ) print(f"Start training on {accelerator.num_processes} GPUs") for epoch in range(num_epochs): model.train() progress_bar = tqdm(dataloader, disable=not accelerator.is_local_main_process) # 只在主进程显示进度条 for batch in progress_bar: # 4. 前向传播 (不需要手动 .to(device)) outputs = model(**batch) loss = outputs.loss # 5. 反向传播 (关键变化!) # Accelerate 会自动处理梯度同步和缩放 accelerator.backward(loss) optimizer.step() lr_scheduler.step() optimizer.zero_grad() progress_bar.set_description(f"loss: {loss.item():.4f}") # 6. 保存模型 (只让主进程保存) # accelerator.wait_for_everyone():确保所有 GPU 都跑完这一轮,再让主进程去保存 accelerator.wait_for_everyone() if accelerator.is_main_process: # unwrap_model:剥去 Accelerate 的包装,获取原始模型进行保存 unwrapped_model = accelerator.unwrap_model(model) unwrapped_model.save_pretrained(f"./output/bert_ft_epoch_{epoch}") tokenizer.save_pretrained(f"./output/bert_ft_epoch_{epoch}") accelerator.end_training() 

如何启动训练

使用 Accelerate 不需要写复杂的 torchrun 命令,而是使用 accelerate launch

# 使用所有可见 GPU 进行训练 accelerate launch train.py # 指定使用 2 张卡 accelerate launch --num_processes=2 train.py # 指定使用特定的卡 (例如卡0和卡1) CUDA_VISIBLE_DEVICES=0,1 accelerate launch train.py 

关键点解析(对比之前的代码)

特性手动 DDP (之前的代码)Accelerate DDP (现在的代码)
设备管理需要写 local_rank = ... 和 torch.cuda.set_device(...)不需要accelerator.prepare 自动处理。
模型封装需要手动处理 device_map={"": local_rank}不需要prepare 自动封装 DDP 并分配设备。
反向传播loss.backward()accelerator.backward(loss) (支持混合精度自动缩放)。
多卡同步需要手动 dist.all_reduce 或 gather_for_metrics提供了 accelerator.gatheraccelerator.wait_for_everyone 等便捷 API。
保存模型需要判断 if local_rank == 0使用 if accelerator.is_main_process 和 unwrap_model

为什么推荐用 Accelerate?

对于场景(大模型 QLoRA 微调):

  1. 代码迁移成本低:你只需要把普通的 PyTorch 训练脚本拿来,加上 Accelerator() 初始化,把 .backward() 换成 accelerator.backward(),再把对象 prepare 一下,就能跑多卡了。
  2. 兼容性极好Accelerate 是 Hugging Face 生态的基石,Trainer 底层就是用它写的。它能无缝配合 DeepSpeedFSDP 等高级并行策略,以后你想改用 DeepSpeed ZeRO-3,只需要改一行启动参数,不需要改训练代码。

=========================================================================

改用 DeepSpeed ZeRO-3

改用 DeepSpeed ZeRO-3,只需要改一行启动参数,不需要改训练代码
第一步:创建一个 DeepSpeed 配置文件

创建一个名为 ds_zero3_config.json 的文件(文件名随意),写入 ZeRO-3 的配置:

{ "train_batch_size": 16, "train_micro_batch_size_per_gpu": 1, "gradient_accumulation_steps": 16, "optimizer": { "type": "AdamW", "params": { "lr": "2e-5", "betas": [0.9, 0.95], "eps": "1e-8", "weight_decay": "0.01" } }, "scheduler": { "type": "WarmupDecayLR", "params": { "total_num_steps": 10000, "warmup_min_lr": "0", "warmup_max_lr": "2e-5", "warmup_num_steps": 500 } }, "fp16": { "enabled": true, "loss_scale": 0, "initial_scale_power": 16, "loss_scale_window": 1000, "hysteresis": 2, "min_loss_scale": 1 }, "bf16": { "enabled": false }, "zero_optimization": { "stage": 3, "offload_optimizer": { "device": "cpu", "pin_memory": true }, "offload_param": { "device": "cpu", "pin_memory": true }, "overlap_comm": true, "contiguous_gradients": true, "sub_group_size": 1e9, "reduce_bucket_size": "auto", "stage3_prefetch_bucket_size": "auto", "stage3_param_persistence_threshold": "auto" }, "gradient_clipping": 1.0, "steps_per_print": 10 } 
第二步:修改启动命令(只改这一行!)

原本的启动命令(DDP 模式):

accelerate launch train.py 

现在的启动命令(DeepSpeed ZeRO-3 模式):
只需要加上 --config_file 参数指向你的 JSON 文件:

accelerate launch --config_file ds_zero3_config.json train.py 
第三步:Python 代码完全不用变

 train.py 内容依然是这样的,完全不用改动:

# train.py 内容(与 DDP 时完全一致) from accelerate import Accelerator # 初始化 Accelerator # 即使你加了 ds_config,这里也不需要传任何参数,accelerate 会自动读取启动命令里的配置 accelerator = Accelerator() model, optimizer, dataloader = accelerator.prepare(model, optimizer, dataloader) # ... 后面的训练循环代码完全一样 ... 

为什么这么神奇?

  1. 自动识别:当你运行 accelerate launch --config_file ds_zero3_config.json 时,Accelerate 库会读取这个 JSON 文件。
  2. 底层替换:看到 zero_optimization.stage = 3 后,Accelerate 会自动将你的模型包装成 DeepSpeedEngine,而不是普通的 DistributedDataParallel
  3. 参数分片:DeepSpeed 引擎会接管模型参数,将它们切分并散落在各个 GPU(甚至 CPU)上,而你的 Python 代码依然觉得自己是在操作一个完整的模型。

总结

  • DDP 模式:每张卡存一份完整模型副本(显存占用大)。
  • ZeRO-3 模式:模型参数被切分到多张卡(显存占用极小,能训更大的模型)。
  • 代码工作量:对于开发者来说,工作量为 0。你只需要准备好配置文件,然后改一行启动命令。

=========================================================================

以上内容总结:

核心结论:
对于 train.py(训练逻辑代码)Accelerate DDP 和 Accelerate DeepSpeed 的代码是 100% 完全一样 的。

区别仅在于:

  1. 外部配置文件:DeepSpeed 需要一个 ds_config.json,DDP 不需要。
  2. 启动命令:DeepSpeed 需要指定配置文件,DDP 不需要。
  3. (可选)初始化方式:如果你不想用 JSON 文件,想把配置写在 Python 里,那么 Accelerator() 的初始化代码会有一点点不同。

1. 训练代码对比 (完全相同)

无论用 DDP 还是 DeepSpeed,你的 train.py 内容通常长这样,完全不需要修改

# train.py (DDP 和 DeepSpeed 共用) import torch from torch.utils.data import DataLoader from transformers import AutoModelForSequenceClassification, AutoTokenizer, get_scheduler from accelerate import Accelerator # --- 初始化部分 --- # 注意:这里代码是一样的! # Accelerate 会自动检测你是用 DDP 还是 DeepSpeed accelerator = Accelerator() # --- 数据和模型准备 --- model = AutoModelForSequenceClassification.from_pretrained("bert-base-chinese") optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5) dataloader = DataLoader(...) # prepare() 会自动根据后端包装模型 model, optimizer, dataloader = accelerator.prepare(model, optimizer, dataloader) # --- 训练循环 --- for batch in dataloader: outputs = model(**batch) loss = outputs.loss accelerator.backward(loss) # 这一行对两者都通用 optimizer.step() optimizer.zero_grad() 

2. 真正的区别在于:配置与启动

虽然代码没变,但它们跑起来的方式截然不同。

模式 A:Accelerate DDP (标准模式)

特点:不需要额外文件,配置简单。

  1. 启动命令
 accelerate launch train.py 
  1. 或者配置混合精度
 accelerate launch --mixed_precision=fp16 train.py 
  1. 代码(可选):如果你想显式指定参数,可以直接写在 Accelerator() 里:
 accelerator = Accelerator(mixed_precision="fp16", gradient_accumulation_steps=2) 
模式 B:Accelerate DeepSpeed (ZeRO 模式)

特点:需要一个 JSON 配置文件来告诉 DeepSpeed 怎么切分参数。

  1. 编写配置文件 ds_config.json (这是 DDP 没有的):
 { "train_batch_size": 16, "train_micro_batch_size_per_gpu": 2, "gradient_accumulation_steps": 8, "optimizer": { "type": "AdamW", "params": { "lr": "2e-5" } }, "fp16": { "enabled": true }, "zero_optimization": { "stage": 3 // 开启 ZeRO-3 } } 
  1. 启动命令 (必须加上 --config_file):
 accelerate launch --config_file ds_config.json train.py 
  1. 代码(此时 train.py 内部)
 # 当 DeepSpeed 启动时,这行代码会读取 config 文件,而不是括号里的参数 accelerator = Accelerator() 

3. 进阶区别:代码中的 DeepSpeedPlugin

如果不想用 JSON 文件,坚持要把所有配置都写在 Python 代码里,那么 Accelerator 的初始化会有区别:

DDP 的 Python 写法:
from accelerate import Accelerator accelerator = Accelerator( mixed_precision="fp16", gradient_accumulation_steps=4 ) 
DeepSpeed 的 Python 写法 (代码有区别):

需要引入 DeepSpeedPlugin 并将其传给 Accelerator

from accelerate import Accelerator from accelerate.utils import DeepSpeedPlugin # 1. 定义 DeepSpeed 配置字典 (对应 JSON 内容) ds_config = { "train_batch_size": 16, "train_micro_batch_size_per_gpu": 2, "gradient_accumulation_steps": 8, "optimizer": { "type": "AdamW", "params": { "lr": "2e-5" } }, "fp16": {"enabled": True}, "zero_optimization": { "stage": 3 } } # 2. 创建 Plugin 对象 ds_plugin = DeepSpeedPlugin(ds_config) # 3. 传给 Accelerator (这里和 DDP 不同) accelerator = Accelerator(deepspeed_plugin=ds_plugin) # 后面的 prepare 和训练循环完全一样 model, optimizer, dataloader = accelerator.prepare(model, optimizer, dataloader) 

总结

特性Accelerate DDPAccelerate DeepSpeed
Python 训练逻辑完全相同 (preparebackward)完全相同 (preparebackward)
配置方式CLI 参数 或 Accelerator() 参数必须用 JSON 文件 或 DeepSpeedPlugin 对象
显存优化有限 (主要靠混合精度/梯度累积)极强 (ZeRO 切分优化器/梯度/参数)
推荐写法accelerator = Accelerator()accelerator launch --config_file ds.json

一句话概括
Accelerate 的魔法在于解耦train.py 只是描述“怎么训练”,而 DDP 还是 DeepSpeed 只是“怎么分配硬件资源”,两者通过 accelerator.prepare 这个适配器连接,所以核心代码可以保持不变。

=========================================================================

补充:DDP 和 DeepSpeed区别

DDP (Distributed Data Parallel) 和 DeepSpeed 都是为了解决多卡并行训练的问题,但它们的核心思路适用场景完全不同。

一句话总结:

  • DDP数据并行。每张卡存一份完整模型,显存占用大,适合中小模型。
  • DeepSpeed (ZeRO)状态/参数切分。模型参数被拆散存放在多张卡上,显存占用极小,适合超大模型

1. 核心区别对比表

特性DDP (PyTorch 标准)DeepSpeed (主要是 ZeRO 策略)
核心思想复制 (Replication)切分 (Partitioning / Sharding)
模型存储每张卡都存一份完整的模型参数、梯度和优化器状态。将优化器状态、梯度、参数切分存储在不同的卡上。
显存占用高。O(N)×GPU数。低。单卡显存占用随 GPU 数量增加而减少。
通信量较低。只同步梯度。较高。除了同步梯度,还需要在计算时动态获取/同步参数。
计算速度。通信开销小,计算效率高。稍慢。因为增加了参数收集的通信开销,但换来了能训练大模型的能力。
适用模型7B、13B 等参数量较小、能塞进单卡显存的模型。30B、70B、175B+ 等参数量巨大、单卡塞不下的模型。

2. 生动的比喻:看书训练

假设你要“阅读”(训练)一本 1000 页的书(模型),你有 4 个学生(4 张 GPU)。

DDP 方式(复印机模式)
  • 做法:把这本书复印 4 份,每个学生手里都有 完整的 1000 页
  • 分工:学生 1 读第 1-250 页,学生 2 读第 251-500 页…
  • 缺点:如果书特别厚(比如 10000 页),每个学生手里的书都拿不动(显存溢出,OOM)。
  • 优点:大家各自读各自的,不需要互相借书,速度很快。
DeepSpeed ZeRO-3 方式(拼图模式)
  • 做法:不复印,把书拆开。撕成 4 份,学生 A 拿第 1-250 页,学生 B 拿第 251-500 页…
  • 分工
    • 学生 A 需要看第 260 页时,必须大喊一声:“谁有第 260 页?”
    • 学生 B 说:“我有,发给你!”
    • 学生 A 看完计算完后,把结果传回去,或者把这一页擦除掉。
  • 优点:无论书有多厚(10000 页甚至 100000 页),只要人数(卡数)够多,每个人手里只拿几十页,完全拿得动。
  • 缺点:学生 A 频繁向学生 B 借书(通信),会花费一些时间,导致阅读速度比 DDP 慢一点。

3. 技术细节上的区别

DDP 的问题:显存被“三巨头”吃光了

训练一个模型,显存主要被这三样东西占用(按比例大致为 2:1:1):

  1. 模型参数:比如 FP16 下,7B 模型约占 14GB。
  2. 优化器状态:Adam 优化器需要存储动量等,通常占用 2 倍于参数的显存(约 28GB)。
  3. 梯度:占用 1 倍于参数的显存(约 14GB)。

DDP 的痛点
在 DDP 模式下,每张卡都要存这三样东西。
如果模型是 7B:

  • 单卡需要:14GB (参数) + 28GB (优化器) + 14GB (梯度) = 56GB
  • 这就是为什么单张 A100 (40GB/80GB) 用 DDP 有时也会显存不足,或者你无法在单卡上训练太大的模型。
DeepSpeed 的解决方案:ZeRO (Zero Redundancy Optimizer)

DeepSpeed 引入了 ZeRO 技术,把上面的“三巨头”进行切分:

  • ZeRO Stage 1:切分优化器状态
    • 节省显存:4倍。
    • 例子:上面的 56GB -> 28GB。
  • ZeRO Stage 2:切分优化器状态 + 梯度
    • 节省显存:8倍。
    • 例子:上面的 56GB -> 14GB。
  • ZeRO Stage 3:切分优化器状态 + 梯度 + 模型参数
    • 节省显存:与 GPU 数量成正比。
    • 例子:如果有 4 张卡,每张卡只需存 1/4 的模型。这就是之前说的“只改一行代码就能跑 70B 模型”的秘密。

4. 应该怎么选?

  1. 如果你训练的是 7B 以下的模型
    • 首选 DDP
    • 代码简单,速度快,不容易出 Bug。如果显存不够,加 gradient_accumulation 或用 4bit 量化(QLoRA)。
  2. 如果你想训练 30B、70B 甚至更大的模型
    • 必须用 DeepSpeed (ZeRO-3)
    • DDP 根本塞不进去。只有 DeepSpeed 能把模型切碎了存。
  3. 如果你想用 QLoRA (4bit 量化) 跑大模型
    • 实际上你不需要 ZeRO-3。
    • 因为 QLoRA 已经把模型压缩到了 4bit,显存占用很小了,普通的 DDP 就能跑 70B 模型。只有在不量化的全量微调场景下,ZeRO 才是大杀器。

=========================================================================

DDP 和 DeepSpeed(尤其是 ZeRO-3)确实都需要进行通信(GPU 之间互相传数据)。

既然都要通信,为什么 DeepSpeed 能塞进去大模型,而 DDP 不行?

核心区别在于:通信的“内容”通信的“时机” 以及 通信的“量”

可以用一个形象的比喻来对比:学生做作业(计算)与对答案(通信)

1. DDP:只对“答案” (通信量小,频率低)

假设有 4 个学生(4 张 GPU),大家手里都有一本完整的书(模型)。

  • 通信内容:只传递梯度(即“答案的修正值”)。
  • 通信时机:在大家做完题(反向传播结束)之后。
  • 过程
    1. 大家各自算各自的题(前向+反向)。
    2. 算完后,大家聚在一起(All-Reduce),互相核对一下:“第 1 题的梯度是 0.5 吗?”“是的,平均一下。”
    3. 核对完,大家各自修改自己书上的知识点(更新参数)。
    4. 下一轮继续

总结

  • 优点:通信量较小(只有梯度),且只在最后通信一次,所以计算效率最高
  • 缺点:每个人都必须拿一本完整的书(存完整模型)。如果书太厚(模型太大),书包(显存)就装不下了。

2. DeepSpeed (ZeRO-3):连“书页”都要借 (通信量大,频率高)

还是 4 个学生,但书太厚了,书包根本装不下。于是大家决定把书撕了,每人只拿几页

  • 通信内容模型参数 + 梯度 + 优化器状态(即“书页”、“答案修正值”、“草稿纸”)。
  • 通信时机:在做题的整个过程中持续发生。
  • 过程
    1. 学生 A 做题做到第 10 页,但他手里只有第 1-5 页。
    2. 通信发生:学生 A 必须大喊:“谁有第 10 页?”
    3. 学生 B 说:“我有,传给你。”(获取参数
    4. 学生 A 拿到第 10 页,开始做题。
    5. 做完后,学生 A 算出第 10 页的修正值。
    6. 通信再次发生:学生 A 把修正值传给学生 B:“这是第 10 页的修改意见,你记一下。”(更新梯度/状态

总结

  • 优点:每个人手里只需要拿几张纸(极省显存),大家可以一起读一本超级厚的书(超大模型)。
  • 缺点:做题过程中,不停地要“借书页”、“还书页”,通信量变得非常巨大,且非常频繁。

3. 深度对比:DDP vs DeepSpeed (ZeRO-3)

特性DDP (DataParallel)DeepSpeed ZeRO-3
手里有什么完整模型 (Parameters)1/N 的模型碎片 (Sharded Parameters)
传什么数据只传梯度 (Gradients)传参数 (Forward时) + 传梯度 (Backward时)
什么时候传一个 Batch 结束后传一次每一层计算前/后都要传
通信压力 (通信带宽占用低)极大 (极度依赖 NVLink/高速网络)
为什么快/慢计算快 (主要时间在计算,通信时间短)相对慢 (大量时间花在等待借书页上)
为什么能省显存不省 (每卡存完整模型) (每卡只存一部分模型)

4. 结论:时间换空间

  • DDP 是为了速度。它的通信是“低成本”的,但它对显存容量要求高。
  • DeepSpeed 是为了容量。它牺牲了通信效率(变慢了),通过疯狂地传递数据碎片,换取了“显存可以无限叠加”的能力。

所以,如果单卡显存够用,大家都会选 DDP(因为它通信少,跑得快);只有当模型大到 DDP 塞不进去时,才会被迫使用 DeepSpeed(虽然通信多,但至少能跑起来)。

=========================================================================

补充:DDP与DP区别

DP (DataParallel) 和 DDP (DistributedDataParallel) 都是 PyTorch 提供的数据并行训练方式(即:每张卡复制一份完整模型,处理不同的数据)。

虽然目的相同,但DP 是老一代的简易方案,DDP 是新一代的工业级标准方案

一句话总结: 除非你只是想在单机上用 2 张卡快速跑通代码验证想法,否则永远不要用 DP,请直接使用 DDP

1. 核心区别对比表

特性DP (DataParallel)DDP (DistributedDataParallel)
并行方式单进程多线程 (Single Process, Multi-Threaded)多进程 (Multi-Process)
性能瓶颈严重受限于 Python GIL (全局解释器锁)无 GIL 限制,真正的并行计算
通信效率低(梯度传输必须经过 CPU)高(GPU 之间直接通过 NCCL 通信)
代码复杂度极低(一行代码搞定)较高(需要处理进程组、初始化等)
适用场景单机多卡,快速调试单机多卡 & 多机多卡,生产环境
显存均衡不均衡(主卡显存占用通常比其他卡高)均衡(所有卡显存占用一致)

2. 生动的比喻:搬砖

假设你要搬 10000 块砖(训练数据),你有 4 个工人(4 张 GPU)。

DP 的模式(包工头模式)
  • 结构:只有一个包工头(主进程),他雇佣了 3 个临时工(子线程)
  • 工作流
    1. 包工头把砖分给临时工。
    2. 临时工干完活,把结果(梯度)拿回来交给包工头。
    3. 包工头自己负责汇总结果,计算更新方法,然后再把新方法告诉临时工。
  • 缺点:包工头(CPU/主线程)累得半死,而因为 Python 的 GIL 锁,同一时间只能有一个人在说话,其他人得排队。导致临时工经常在等包工头,效率极低。
DDP 的模式(合作小组模式)
  • 结构:4 个独立的合伙人(独立的进程),大家地位平等。
  • 工作流
    1. 每个人各领一部分砖(通过 DistributedSampler)。
    2. 大家自己干自己的活。
    3. 干完后,大家聚在一起开个会(All-Reduce),核对一下笔记,统一更新一下知识。
    4. 散会,继续干活。
  • 优点:没有包工头卡脖子,大家同步交流,效率极高。

3. 为什么 DP 性能差?(技术细节)

  1. Python GIL 锁
    • DP 使用多线程。Python 的多线程由于 GIL 的存在,同一时刻只能有一个线程在执行 Python 字节码。这意味着在梯度同步和参数更新时,CPU 是串行的,无法利用多核优势。
    • DDP 使用多进程。每个进程有自己独立的 Python 解释器和 GIL,互不干扰,真正实现了并行。
  2. 梯度传输路径
    • DP:GPU 0, 1, 2 的梯度必须先传输到 CPU 内存,由 CPU 汇总计算平均,然后再发回 GPU。这增加了 GPU -> CPU -> GPU 的数据拷贝开销。
    • DDP:使用 NVIDIA 的 NCCL 库,允许 GPU 0 直接通过 NVLink 或 PCIe 网络与其他 GPU 通信,不需要 CPU 中转。
  3. 显存溢出风险
    • 在 DP 中,主卡(GPU 0)除了存模型,还要负责损失计算、梯度汇总和更新,因此主卡的显存占用通常比其他卡高。容易导致“其他卡没事,主卡先爆显存(OOM)”的情况。

4. 代码对比

DP 的写法(极其简单,但慢)
import torch.nn as nn # 定义模型 model = MyModel() # 只需要这一行! if torch.cuda.device_count() > 1: model = nn.DataParallel(model) # 自动包裹模型 model.to("cuda") # 后面正常训练,PyTorch 会自动切分数据输入到不同卡 
DDP 的写法(稍微复杂,但快)
import torch import torch.distributed as dist from torch.nn.parallel import DistributedDataParallel as DDP # 1. 初始化进程组 dist.init_process_group("nccl") local_rank = int(os.environ["LOCAL_RANK"]) # 2. 配置当前进程使用的 GPU torch.cuda.set_device(local_rank) device = torch.device(f"cuda:{local_rank}") # 3. 创建模型并移动到 GPU model = MyModel().to(device) # 4. 用 DDP 包裹模型 model = DDP(model, device_ids=[local_rank]) # 5. 数据加载器需要使用 DistributedSampler # (需要手动切分数据集,确保每个进程拿到的数据不同) # ... (后续训练代码) # 6. 记得最后要销毁进程组 dist.destroy_process_group() 

*(注:实际上我们通常会使用 Trainer 或 Accelerate 来自动处理 DDP 的繁琐步骤,而不需要手写上面那么多代码)*

5. 总结与建议

  • DP (DataParallel)
    • 优点:API 简单,改动代码少。
    • 缺点:慢,受限于 GIL,显存不均,不支持多机训练。
    • 适用:只是想在双卡机器上跑个小 demo,不关心速度,或者显卡非常少(1-2张)且模型很小。
  • DDP (DistributedDataParallel)
    • 优点:速度快,支持大模型,支持多机多卡,显存均衡。
    • 缺点:上手概念稍多。
    • 适用:几乎所有正式的科研训练和工业级部署。

结论: 只要涉及到多卡训练,默认使用 DDP(或者封装了 DDP 的工具,如 Trainer / Accelerate)。请忘掉 DP 的存在。

Read more

《算法题讲解指南:优选算法-位运算》--35.两个整数之和,36.只出现一次的数字 ||,37.消失的两个数字

《算法题讲解指南:优选算法-位运算》--35.两个整数之和,36.只出现一次的数字 ||,37.消失的两个数字

🔥小叶-duck:个人主页 ❄️个人专栏:《Data-Structure-Learning》 《C++入门到进阶&自我学习过程记录》《算法题讲解指南》--从优选到贪心 ✨未择之路,不须回头 已择之路,纵是荆棘遍野,亦作花海遨游 目录 35.两个整数之和 题目链接: 题目描述: 题目示例: 解法(位运算): 算法思路: C++算法代码: 算法总结及流程解析: 36.只出现一次的数字 || 题目链接: 题目描述: 题目示例: 解法(比特位计数): 算法思路: C++算法代码: 算法总结及流程解析: 38. 消失的两个数字 题目链接: 题目描述: 题目示例: 解法(位运算): 算法思路: C++算法代码: 算法总结及流程解析: 结束语

By Ne0inhk
Flutter 三方库 sm_crypto 的鸿蒙化适配指南 - 实现国产密码算法 SM2/SM3/SM4 的端侧加解密、支持数字签名与国密 SSL 安全通信实战

Flutter 三方库 sm_crypto 的鸿蒙化适配指南 - 实现国产密码算法 SM2/SM3/SM4 的端侧加解密、支持数字签名与国密 SSL 安全通信实战

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 sm_crypto 的鸿蒙化适配指南 - 实现国产密码算法 SM2/SM3/SM4 的端侧加解密、支持数字签名与国密 SSL 安全通信实战 前言 在进行针对中国市场的 Flutter for OpenHarmony 企业级或政务级应用开发时,支持国产密码算法(国密)是硬性的合规要求。sm_crypto 是一个功能完备的国密算法 Dart 实现库。它涵盖了非对称加密 SM2、哈希摘要 SM3 以及对称加密 SM4。本文将探讨如何在鸿蒙端利用该库构建符合国家标准的安全加密体系。 一、原原理性解析 / 概念介绍 1.1 基础原理 sm_crypto 严格遵循国家密码管理局发布的 GM/

By Ne0inhk
【Python】探索自然语言处理的利器:THULAC 中文词法分析库详解

【Python】探索自然语言处理的利器:THULAC 中文词法分析库详解

THULAC(THU Lexical Analyzer for Chinese)是清华大学开发的一款中文词法分析工具,集成了分词和词性标注两大功能。THULAC 拥有强大的分词能力和高效的词性标注,适用于多种中文文本处理场景。该工具能够在保证高准确率的同时保持较快的处理速度,非常适合大规模中文数据处理。 ⭕️宇宙起点 * 🔨 THULAC 的特点 * 📦 安装与配置 * 1. 使用 pip 安装 * 2. 使用 GitHub 源码安装 * ♨️ 使用方法 * 1. 分词与词性标注 * 2. 只进行分词(无词性标注) * 3. 使用自定义词典 * 4. 参数详解 * 5. 文件分词与命令行使用 * 命令行方式 * 🚩 性能评测与对比 * 🧱 典型应用场景 * 📥 下载地址 * 💬 结语 * 📒 参考文献 🔨 THULAC 的特点 1. 准确率高:在标准数据集(如 CTB5)

By Ne0inhk
机器学习:数据清洗与预处理 | Python

机器学习:数据清洗与预处理 | Python

个人主页-爱因斯晨 文章专栏-Python学习 文章目录 * 个人主页-爱因斯晨 * 文章专栏-Python学习 * 前言 * 了解数据清洗 * 数据清洗的步骤 * 1. 环境准备与库导入 * 2. 数据加载 * 3. 数据初探与理解 * 4. 缺失值处理 * 5. 重复值处理 * 6. 异常值处理 * 7. 数据类型转换 * 8. 数据标准化 / 归一化(预处理) * 实例实践 * 总结 前言 我们不论在学习机器学习还是数据分析中,都会涉及很多数据。但原数据不可避免有很多杂志,为了确保结果的准确性,我们需要首先进行数据清洗和预处理。 了解数据清洗 数据清洗就像是一场数据的“大扫除”。它是从原始数据中找出并修正那些错误、不完整、重复或不一致的数据。通过数据清洗,能显著提升数据质量,为后续数据分析、挖掘和建模等工作提供准确、可靠、干净的数据基础,从而让基于数据得出的结论更具可信度和价值。 数据清洗的步骤 1. 环境准备与库导入

By Ne0inhk