accelerate 分布式技巧(一)

accelerate分布式技巧

简单使用

Accelerate是一个来自Hugging Face的库,它简化了将单个GPU的PyTorch代码转换为单个或多台机器上的多个GPU的代码。
Accelerate精确地抽象了与多GPU/TPU/fp16相关的模板代码,并保持Pytorch其余代码不变。

  import torch
  import torch.nn.functional as F
  from datasets import load_dataset
+ from accelerate import Accelerator

+ accelerator = Accelerator()
- device = 'cpu'
+ device = accelerator.device

  model = torch.nn.Transformer().to(device)
  optimizer = torch.optim.Adam(model.parameters())

  dataset = load_dataset('my_dataset')
  data = torch.utils.data.DataLoader(dataset, shuffle=True)

+ model, optimizer, data = accelerator.prepare(model, optimizer, data)

  model.train()
  for epoch in range(10):
      for source, targets in data:
          source = source.to(device)
          targets = targets.to(device)

          optimizer.zero_grad()

          output = model(source)
          loss = F.cross_entropy(output, targets)

-         loss.backward()
+         accelerator.backward(loss)

          optimizer.step()

通过添加上面5行代码,可以在任何类型的单节点或分布式节点设置(单CPU、单GPU、多GPU和TPU)上运行,也可以使用或不使用混合精度(fp16)如:Accelerator(cpu=args.cpu, mixed_precision=args.mixed_precision)

+ eval_dataloader = accelerator.prepare(eval_dataloader)
  predictions, labels = [], []
  for source, targets in eval_dataloader:
      with torch.no_grad():
          output = model(source)

-     predictions.append(output.cpu().numpy())
-     labels.append(targets.cpu().numpy())
+     predictions.append(accelerator.gather(output).cpu().numpy())
+     labels.append(accelerator.gather(targets).cpu().numpy())

  predictions = np.concatenate(predictions)
  labels = np.concatenate(labels)

+ predictions = predictions[:len(eval_dataloader.dataset)]
+ labels = label[:len(eval_dataloader.dataset)]

  metric_compute(predictions, labels)

config参数

您需要配置 Accelerate以了解当前系统是如何为训练设置的。为此,运行以下命令并回答提示给您的问题:
accelerate config --config_file xxx

## accelerate config --help
options:
  -h, --help            show this help message and exit
  --config_file CONFIG_FILE
                        The path to use to store the config file. Will default to a file named
                        default_config.yaml in the cache location, which is the content of the environment
                        `HF_HOME` suffixed with 'accelerate', or if you don't have such an environment
                        variable, your cache directory ('~/.cache' or the content of `XDG_CACHE_HOME`)
                        suffixed with 'huggingface'.

subcommands:
  {default,update}
    default             Create a default config file for Accelerate with only a few flags set.
    update              Update an existing config file with the latest defaults while maintaining the old
                        configuration.

要编写不包括DeepSpeed配置或在tpu上运行等选项的裸机配置,您可以快速运行:
python -c "from accelerate.utils import write_basic_config; write_basic_config(mixed_precision='fp16')"

得到default_config.yaml文件:

{
  "compute_environment": "LOCAL_MACHINE",
  "debug": false,
  "distributed_type": "MULTI_GPU",
  "downcast_bf16": false,
  "machine_rank": 0,
  "main_training_function": "main",
  "mixed_precision": "fp16",
  "num_machines": 1,
  "num_processes": 4,
  "rdzv_backend": "static",
  "same_network": false,
  "tpu_use_cluster": false,
  "tpu_use_sudo": false,
  "use_cpu": false
}

要检查您的配置是否正常,请运行
accelerate env

- `Accelerate` version: 0.22.0
- Platform: Linux-3.10.0-1160.el7.x86_64-x86_64-with-glibc2.17
- Python version: 3.10.12
- Numpy version: 1.25.2
- PyTorch version (GPU?): 2.0.1 (True)
- PyTorch XPU available: False
- PyTorch NPU available: False
- System RAM: 503.65 GB
- GPU type: NVIDIA A10
- `Accelerate` default config:
        Not found

一旦完成,您就可以通过运行来测试设置上的一切是否正常:

accelerate test --config_file path_to_config.yaml

accelerate launch 启动

accelerate launch --config_file default_config.yaml \
  path_to_script.py \
  --args_for_the_script

API 说明

导入Accelerator主类并在accelerate 对象中实例化一个, 该类的所有参数可以参考https://huggingface.co/docs/accelerate/v0.22.0/en/package_reference/accelerator#accelerate.Accelerator

from accelerate import Accelerator
accelerator = Accelerator()

将所有与训练相关的对象(优化器、模型、训练数据加载器、学习率调度器)传递给prepare()方法。这将确保一切为训练做好准备。

model, optimizer, train_dataloader, lr_scheduler = accelerator.prepare(
    model, optimizer, train_dataloader, lr_scheduler
)

特别是,您的训练数据加载器将在所有可用的 GPU/TPU 核心上进行分片,以便每个核心都能看到训练数据集的不同部分。此外,所有进程的随机状态将在每次迭代开始时通过数据加载器进行同步,以确保数据以相同的方式进行洗牌(如果您决定使用 shuffle=True 或任何类型的随机采样器)。

训练的实际批大小将是使用的设备数量乘以您在脚本中设置的批大小:例如,在4个gpu上进行训练,在创建训练数据加载器时,批大小为16,将以实际批大小为64进行训练。

或者,您可以在创建和初始化加速器时使用选项 split_batches=True,在这种情况下,无论您在 1、2、4 还是 64 个 GPU 上运行脚本,批处理大小将始终保持不变。

Replace the lineloss.backward()by accelerator.backward(loss).

分布式评估

validation_dataloader = accelerator.prepare(validation_dataloader)

至于您的训练数据加载器,这意味着(如果您在多个设备上运行脚本)每个设备将只能看到部分评估数据。这意味着您需要将您的预测组合在一起。使用gather_for_metrics()方法非常容易做到这一点。

for inputs, targets in validation_dataloader:
    predictions = model(inputs)
    # Gather all predictions and targets
    all_predictions, all_targets = accelerator.gather_for_metrics((predictions, targets))
    # Example of use with a *Datasets.Metric*
    metric.add_batch(all_predictions, all_targets)

只在一个进程上执行一条语句

if accelerator.is_local_main_process: # 本机多进程
    # Is executed once per server

另一个例子是进度条:为了避免在输出中出现多个进度条,您应该只在本地主进程中显示一个

from tqdm.auto import tqdm

progress_bar = tqdm(range(args.max_train_steps), disable=not accelerator.is_local_main_process)

如果你在两台有多个gpu的服务器上运行你的训练,指令将在每台服务器上执行一次。如果您只需要对所有进程(而不是每台机器)执行一次,例如,将最终模型上载到模型中心,那么将其封装在这样的测试中

if accelerator.is_main_process:
    # Is executed once only

推迟执行

当您运行常规脚本时,指令是按顺序执行的。使用Accelerate同时在多个gpu上部署脚本会带来一些复杂性:虽然每个进程按顺序执行所有指令,但有些进程可能比其他进程更快。

在执行给定的指令之前,可能需要等待所有进程到达某个点。例如,在确定每个过程都通过训练完成之前,您不应该保存模型。为此,只需在代码中编写以下行代码

accelerator.wait_for_everyone()

该指令将阻塞所有先到达的进程,直到所有其他进程到达该点(如果您只在一个GPU或CPU上运行脚本,这将不会做任何事情)。

保存训练的模型可能需要一些调整:首先,您应该等待所有进程都达到脚本中的该点,如上所示,然后,您应该在保存模型之前解开模型。这是因为在执行prepare()方法时,您的模型可能已被放置在一个更大的模型中,该模型处理分布式训练。这反过来意味着,在不采取任何预防措施的情况下保存模型状态字典将考虑到潜在的额外层,并且最终将得到无法加载回基本模型的权重。 save_model() 方法将帮助您实现这一目标。它将解开您的模型并保存模型状态字典。

Saving/loading a model

accelerator.wait_for_everyone()
accelerator.save_model(model, save_directory)

save_model()方法还可以将模型保存到分片检查点或使用安全系数格式。如下例子:

accelerator.wait_for_everyone()
accelerator.save_model(model, save_directory, max_shard_size="1GB", safe_serialization=True)

如果您的脚本包含加载检查点的逻辑,我们还建议您在展开的模型中加载权重(这仅在您使模型经过prepare()后使用加载函数时才有用)。这是一个例子:

unwrapped_model = accelerator.unwrap_model(model)
path_to_checkpoint = os.path.join(save_directory,"pytorch_model.bin")
unwrapped_model.load_state_dict(torch.load(path_to_checkpoint))

请注意,由于所有模型参数都是对张量的引用,因此这会将您的权重加载到模型中。

如果您想将分片检查点或安全张量格式的检查点加载到具有特定设备的模型中,我们建议您使用 load_checkpoint_in_model() 函数加载它。这是一个例子:

load_checkpoint_in_model(unwrapped_model, save_directory, device_map={"":device})

Saving/loading entire states

训练模型时,您可能希望保存模型、优化器、随机生成器和潜在的 LR 调度器的当前状态,以便在同一脚本中恢复。您可以分别使用 save_state()load_state() 来执行此操作。

要进一步自定义通过 save_state() 保存状态的位置和方式,可以使用 ProjectConfiguration 类。例如,如果启用automatic_checkpoint_naming,则每个保存的检查点将位于Accelerator.project_dir/checkpoints/checkpoint_{checkpoint_number}.


from accelerate.utils import ProjectConfiguration
accelerator_project_config = ProjectConfiguration(
    total_limit=args.checkpoints_total_limit
)
accelerator = Accelerator(
    gradient_accumulation_steps=args.gradient_accumulation_steps,
    mixed_precision=args.mixed_precision,
    log_with=args.report_to,
    logging_dir=logging_dir,
    project_config=accelerator_project_config,
)

如果您已通过 register_for_checkpointing() 注册了要存储的任何其他有状态项目,它们也将被保存和/或加载。

from accelerate import Accelerator

accelerator = Accelerator()
# Assume `CustomObject` has a `state_dict` and `load_state_dict` function.
obj = CustomObject()
accelerator.register_for_checkpointing(obj)
accelerator.save_state("checkpoint.pt")

传递给 register_for_checkpointing() 的每个对象都必须有一个要存储的 load_state_dictstate_dict 函数

梯度裁剪
如果您在脚本中使用梯度裁剪,则应分别将对 torch.nn.utils.clip_grad_norm_ torch.nn.utils.clip_grad_value_ 的调用替换为clipgrad_norm()clipgrad_value()

from accelerate import Accelerator

accelerator = Accelerator(gradient_accumulation_steps=2)
dataloader, model, optimizer, scheduler = accelerator.prepare(dataloader, model, optimizer, scheduler)

for input, target in dataloader:
    optimizer.zero_grad()
    output = model(input)
    loss = loss_func(output, target)
    accelerator.backward(loss)
    if accelerator.sync_gradients: # 在每个训练步骤中同步梯度, 目前是否在所有过程中同步梯度
        accelerator.clip_grad_norm_(model.parameters(), max_grad_norm)
    optimizer.step()

混合精度训练
如果您使用 Accelerate 在混合精度中运行训练,您将在模型内部计算损失(例如在 Transformer 模型中),从而获得最佳结果。模型外部的每个计算都将以全精度执行(这通常是损失计算所需的,特别是当它涉及 softmax 时)。但是,您可能希望将损失计算放入 autocast() 上下文管理器中:

from accelerate import Accelerator

accelerator = Accelerator(mixed_precision="fp16")
with accelerator.autocast():
    train()

混合精度训练的另一个警告是,梯度在开始时和有时在训练过程中会跳过一些更新:由于动态损失缩放策略,训练期间存在梯度溢出的点,并且损失缩放因子减小为避免在下一步中再次发生这种情况。
这意味着您可以在没有更新时更新您的学习率调度程序,这通常很好,但当您的训练数据很少或调度程序的第一个学习率值非常重要时,可能会产生影响。在这种情况下,当优化器步骤未完成时,您可以跳过学习率调度程序更新,如下所示:

if not accelerator.optimizer_step_was_skipped:
    lr_scheduler.step()

梯度累积
要执行梯度累积,请使用accumulate()并指定gradient_accumulation_steps。这还将自动确保在多设备训练时梯度同步或不同步,检查是否应该实际执行该步骤,并自动缩放损失:

accelerator = Accelerator(gradient_accumulation_steps=2)
model, optimizer, training_dataloader = accelerator.prepare(model, optimizer, training_dataloader)

for input, label in training_dataloader:
    with accelerator.accumulate(model):
        predictions = model(input)
        loss = loss_function(predictions, label)
        accelerator.backward(loss)
        optimizer.step()
        scheduler.step()
        optimizer.zero_grad()

参数、属性和方法说明

  • 设备类型distributed_type
class DistributedType(str, enum.Enum):
    # Subclassing str as well as Enum allows the `DistributedType` to be JSON-serializable out of the box.
    NO = "NO"
    MULTI_CPU = "MULTI_CPU"
    MULTI_GPU = "MULTI_GPU"
    MULTI_NPU = "MULTI_NPU"
    MULTI_XPU = "MULTI_XPU"
    DEEPSPEED = "DEEPSPEED"
    FSDP = "FSDP"
    TPU = "TPU"
    MEGATRON_LM = "MEGATRON_LM"
  • init_trackers初始化跟踪器,
from accelerate import Accelerator
accelerator = Accelerator(log_with="tensorboard")
accelerator.init_trackers(
	project_name="my_project",
	config={"learning_rate": 0.001, "batch_size": 32},
    init_kwargs={"tensorboard": {"flush_secs": 60}},)

tensorboard_tracker = accelerator.get_tracker("tensorboard")
  • end_training结束训练
在这里插入代码片

其他接口参考: https://huggingface.co/docs/accelerate/v0.22.0/en/package_reference/accelerator

进阶

指定GPU和最大显存

当一张显卡容不下一个模型时,我们需要用多张显卡来推理。

假如我们现在模型是一个Llama33B,那么我们推理一般需要使用66G的显存,假如我们想要使用6号和7号卡,每张卡允许使用的显存是35G。那么我们代码可以这样写:

from transformers import LlamaConfig,LlamaForCausalLM,LlamaTokenizer
from accelerate import init_empty_weights,infer_auto_device_map,load_checkpoint_in_model,dispatch_model, load_checkpoint_and_dispatch
import torch

cuda_list = '1,2'.split(',')
memory = '8GiB'
model_path = './custom_model/hf_llama_7b'
no_split_module_classes = LlamaForCausalLM._no_split_modules
print(no_split_module_classes)
max_memory = {int(cuda):memory for cuda in cuda_list}
config = LlamaConfig.from_pretrained(model_path)
with init_empty_weights():
    model = LlamaForCausalLM._from_config(config, torch_dtype=torch.float16) #加载到meta设备中,不需要耗时,不需要消耗内存和显存

device_map = infer_auto_device_map(model, max_memory=max_memory,no_split_module_classes=no_split_module_classes) #自动划分每个层的设备
load_checkpoint_in_model(model, model_path,device_map=device_map) #加载权重
model = dispatch_model(model, device_map=device_map) #并分配到具体的设备上

tokenizer = LlamaTokenizer.from_pretrained(model_path)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token
torch.set_grad_enabled(False)
model.eval()
sents=['你是谁']
ids = tokenizer(sents, 
                max_length=1800,  
                padding=True, 
                truncation=True, 
                return_tensors="pt")
ids = ids.to(model.device) 
outputs = model.generate(**ids, do_sample=False)
print(outputs)

但是里面写的不是很齐全,如果用了infer_auto_device_map,就不需要用load_checkpoint_and_dispatch了,否则其他显卡还是会被占用少量显存,而且也不需要手动指定如何分割,挺方便的。

<think>嗯,用户想要个关于SFT(监督式微调)的代码示例,特别是针对DeepSeek-R1-7B模型,从数据准备开始。首先,我需要确认SFT的基本流程,包括数据准备、模型加载、训练设置等步骤。用户可能希望了解具体的代码实现,所以需要给出实际的代码片段,并解释每个部分的作用。 首先,数据准备部分。SFT通常需要特定格式的数据集,比如指令-回答对。常见的格式是JSON,每个样本包含指令和回答。可能需要使用datasets库加载数据集,并进行预处理,比如分词。要确保数据格式正确,并且处理成模型可以接受的输入形式。 接下来是模型加载部分。DeepSeek-R1-7B是个大模型,可能需要使用Hugging Face的Transformers库。需要确认模型的具体名称,比如“deepseek-ai/deepseek-moe-16b-base”或者其他变体。不过用户提到的是7B,可能需要检查模型的确切名称是否存在,或者是否有对应的版本。如果模型是类似于Llama结构的,可能需要使用AutoModelForCausalLM和相应的tokenizer。 然后是训练配置。使用Hugging Face的Trainer类会比较方便。需要设置训练参数,比如学习率、批次大小、优化器类型等。可能需要使用peft库进行参数高效微调,比如LoRA,这样可以在有限资源下训练大模型。需要确保正确配置LoRA的参数,如r、lora_alpha、target_modules等。 另外,数据处理方面,需要编写个处理函数,将每个样本转换为模型输入。对于生成任务,通常需要将指令和回答拼接起来,并添加EOS token。同时,需要设置正确的标签,通常在生成任务中,标签是输入的移位版本,但需要根据具体情况处理,比如将指令部分的标签设为-100,只计算回答部分的损失。 还需要考虑数据的分割,比如训练集和验证集。使用datasets库的train_test_split方法可以方便地分割数据。预处理后的数据集需要传递给Trainer。 可能遇到的问题包括模型名称是否正确,tokenizer是否匹配,数据格式是否正确,以及训练时的内存问题。用户可能在资源有限的情况下运行代码,所以使用LoRA和fp16混合精度训练是必要的。此外,需要提醒用户可能需要调整batch_size和gradient_accumulation_steps来适应显存限制。 最后,测试生成部分,需要确保模型在训练后能正确加载,并使用generate方法生成文本。生成参数如max_length、temperature等需要合理设置。 需要检查代码中的每个部分是否语法正确,比如是否正确导入库,是否正确处理数据,模型和tokenizer是否正确加载。同时,要确保示例代码简洁明了,用户能够理解每个步骤的作用。可能还需要添加些注释,帮助用户理解关键点,比如LoRA的配置、数据处理函数的结构等。</think>好的!以下是个完整的SFT(监督式微调)代码示例,针对DeepSeek-R1-7B模型的训练流程,包含从数据准备到模型训练的关键步骤: ```python # 环境准备 !pip install transformers datasets peft accelerate bitsandbytes # 1. 数据准备 from datasets import load_dataset # 示例数据格式:指令-回答对 dataset = load_dataset("json", data_files={ "train": "sft_train.json", "validation": "sft_valid.json" }) # 数据样例结构: # { # "instruction": "解释量子力学中的叠加原理", # "output": "叠加原理是指量子系统可以同时处于多个可能状态的线性组合中..." # } # 2. 数据预处理 from transformers import AutoTokenizer model_name = "deepseek-ai/deepseek-llm-7b-base" tokenizer = AutoTokenizer.from_pretrained(model_name) tokenizer.pad_token = tokenizer.eos_token # 设置填充token def process_data(example): # 拼接指令和回答 prompt = f"### Instruction:\n{example['instruction']}\n\n### Response:\n" response = example['output'] + tokenizer.eos_token # 合并并编码 full_text = prompt + response encoding = tokenizer( full_text, truncation=True, max_length=512, padding="max_length" ) # 设置labels(仅计算response部分的loss) prompt_len = len(tokenizer(prompt).input_ids) labels = [-100]*prompt_len + encoding.input_ids[prompt_len:] return {"input_ids": encoding.input_ids, "attention_mask": encoding.attention_mask, "labels": labels} dataset = dataset.map(process_data, batched=True) # 3. 模型加载与配置 from transformers import AutoModelForCausalLM, TrainingArguments from peft import LoraConfig, get_peft_model model = AutoModelForCausalLM.from_pretrained( model_name, load_in_4bit=True, # 4bit量化节省显存 device_map="auto" ) # 配置LoRA参数 peft_config = LoraConfig( r=8, lora_alpha=32, target_modules=["q_proj", "v_proj"], # 选择注意力层的投影矩阵 lora_dropout=0.05, bias="none", task_type="CAUSAL_LM" ) model = get_peft_model(model, peft_config) model.print_trainable_parameters() # 显示可训练参数占比 # 4. 训练配置 training_args = TrainingArguments( output_dir="./deepseek-sft", per_device_train_batch_size=4, per_device_eval_batch_size=4, gradient_accumulation_steps=2, learning_rate=2e-5, num_train_epochs=3, logging_steps=50, evaluation_strategy="epoch", save_strategy="epoch", fp16=True # 启用混合精度训练 ) # 5. 开始训练 from transformers import Trainer trainer = Trainer( model=model, args=training_args, train_dataset=dataset["train"], eval_dataset=dataset["validation"], data_collator=lambda data: { "input_ids": torch.stack([torch.tensor(d["input_ids"]) for d in data]), "attention_mask": torch.stack([torch.tensor(d["attention_mask"]) for d in data]), "labels": torch.stack([torch.tensor(d["labels"]) for d in data]) } ) trainer.train() # 6. 推理测试 prompt = "解释区块链的工作原理" inputs = tokenizer(f"### Instruction:\n{prompt}\n\n### Response:\n", return_tensors="pt").to("cuda") outputs = model.generate( inputs.input_ids, max_new_tokens=200, temperature=0.7, top_p=0.9, do_sample=True ) print(tokenizer.decode(outputs[0], skip_special_tokens=True)) ``` 关键要点说明: 1. **数据准备**: - 需要构建指令-回答对格式的数据集 - 建议至少准备1k-10k条高质量样本 - 推荐使用系统提示词模板(如### Instruction/Response格式) 2. **参数高效微调**: - 使用LoRA技术(4bit量化 + 适配器) - 仅训练约0.1%的参数(原始7B参数中约7M可训练) - 显存消耗可控制在~12GB左右 3. **训练技巧**: - 使用梯度累积(gradient_accumulation_steps)缓解显存压力 - 设置合理的max_length(根据数据长度分布) - 通过labels掩码实现仅计算response部分的loss 4. **注意事项**: - 确保数据质量:低质量数据会显著影响效果 - 监控训练loss:正常范围在1.0-2.5之间波动 - 验证生成效果:通过人工评估调整温度参数等 如果需要更具体的实现细节(如自定义数据集构建、分布式训练配置等),可以补充说明具体需求!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发呆的比目鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值