赞
踩
1.特点
Accelerate提供简单的API使得脚本可以按混合精度运行,同时可在任何分布式设置上运行,相同的代码可以在本地电脑上进行debug也可以在训练环境执行。同时, Accelerate提供一个CLI工具,允许快速配置并测试训练环境,然后运行脚本
2.易于集成
Pytorch中传统的训练Loop
my_model.to(device)
for batch in my_training_dataloader:
my_optimizer.zero_grad()
inputs, targets = batch
inputs = inputs.to(device)
targets = targets.to(device)
outputs = my_model(inputs)
loss = my_loss_function(outputs, targets)
loss.backward()
my_optimizer.step()
对其进行改变以与accelerate共同工作也非常简单,只需要增加下面的代码:
+ from accelerate import Accelerator
+ accelerator = Accelerator()
# Use the device given by the *accelerator* object.
+ device = accelerator.device
my_model.to(device)
# Pass every important object (model, optimizer, dataloader) to *accelerator.prepare*
+ my_model, my_optimizer, my_training_dataloader = accelerate.prepare(
+ my_model, my_optimizer, my_training_dataloader
+ )
for batch in my_training_dataloader:
my_optimizer.zero_grad()
inputs, targets = batch
inputs = inputs.to(device)
targets = targets.to(device)
outputs = my_model(inputs)
loss = my_loss_function(outputs, targets)
# Just a small change for the backward instruction
- loss.backward()
+ accelerator.backward(loss)
my_optimizer.step()
加上这些之后,脚本就可以在一个分布式环境上运行(多个-GPU,TPU)。
您甚至可以通过让Accelerate 为您处理设备放置来稍微简化您的脚本(这更安全,尤其是对于 TPU 训练):
+ from accelerate import Accelerator
+ accelerator = Accelerator()
- my_model.to(device)
# Pass every important object (model, optimizer, dataloader) to *accelerator.prepare*
+ my_model, my_optimizer, my_training_dataloader = accelerate.prepare(
+ my_model, my_optimizer, my_training_dataloader
+ )
for batch in my_training_dataloader:
my_optimizer.zero_grad()
inputs, targets = batch
- inputs = inputs.to(device)
- targets = targets.to(device)
outputs = my_model(inputs)
loss = my_loss_function(outputs, targets)
# Just a small change for the backward instruction
- loss.backward()
+ accelerator.backward(loss)
my_optimizer.step()
3.Script launcher脚本启动器
无需记住如何使用 torch.distributed.launch 或为 TPU 训练编写特定的启动器! Accelerate 附带一个 CLI 工具,可以让您在启动分布式脚本时更轻松。
在您的机器上运行:
accelerate config
并回答提出的问题。 这将生成一个配置文件,该文件将在执行时自动用于正确设置默认选项
accelerate launch my_script.py --args_to_my_script
例如,下面是运行 NLP 示例的方式(从 repo 的根目录):
accelerate launch examples/nlp_example.py
4.Supported integrations支持的集成
5.使用方式
为了在自己的脚本上使用Accelerate,需要改变如下四个东西。
(1)导入Accelerate主类实例化一个加速器对象:
from accelerate import Accelerator
accelerator = Accelerator()
这应该在您的训练脚本中尽早发生,因为它将初始化分布式训练所需的一切。 您无需指明您所处的环境类型(一台机器配备 GPU,一台机器配备多个 GPU,几台机器配备多个 GPU 或 TPU),库会自动检测到这一点。
(2)删除模型和输入数据的调用 .to(device) 或 .cuda() 。 加速器对象将为您处理此问题,并将所有这些对象放置在适合您的设备上。 如果您知道自己在做什么,则可以保留这些 .to(device) 调用,但您应该使用加速器对象提供的设备:
accelerator.device
要完全停用自动设备放置,请在初始化加速器时传递 device_placement=False。
如果您手动将对象放置在正确的设备上,请在将模型放置在 Accelerator.device 上后小心创建优化器,否则您的训练将在 TPU 上失败。
(3)将所有与训练相关的对象(optimizer, model, training dataloader) (优化器、模型、训练数据加载器)传递给 prepare() 方法。 这将确保一切都准备好进行培训。
model, optimizer, train_dataloader = accelerator.prepare(model, optimizer, train_dataloader)
特别是,您的训练数据加载器将跨所有可用的 GPU/TPU 内核进行分片,以便每个人都能看到训练数据集的不同部分。 此外,所有进程的随机状态将在每次迭代开始时通过您的数据加载器同步,以确保数据以相同的方式进行混洗(如果您决定使用 shuffle=True 或任何类型的随机采样器)。
训练的实际批量大小将是使用的设备数量乘以您在脚本中设置的批量大小:例如,在创建训练数据加载器时在批量大小为 16 的 4 个 GPU 上进行训练,将以实际批量大小进行训练 64 个。
任何使用您的训练数据加载器长度的指令(例如,如果您需要总训练步骤数来创建学习率调度程序)应该在调用 prepare() 之后进行。
您可以完美地将数据加载器单独发送到 prepare(),但最好将模型和优化器一起发送到 prepare()。
您可能希望也可能不希望将验证数据加载器发送到 prepare(),具体取决于您是否要运行分布式评估(见下文)。
(4)用accelerator.backward(loss) 替换loss.backward()
你都准备好了! 通过所有这些更改,您的脚本将在您的本地计算机以及多个 GPU 或 TPU 上运行! 您可以使用自己喜欢的工具来启动分布式训练,也可以使用Accelerate 启动器。
6.Distributed evaluation分布式评估
如果您将验证数据加载器排除在 prepare() 方法之外,您可以在训练脚本中执行定期评估。 在这种情况下,您需要手动将输入数据放在加速器.device 上。
要执行分布式评估,请将您的验证数据加载器发送到 prepare() 方法:
validation_dataloader = accelerator.prepare(validation_dataloader)
就像您的训练数据加载器一样,这意味着(如果您在多个设备上运行脚本)每个设备将只能看到部分评估数据。 这意味着您需要将您的预测组合在一起。 使用gather() 方法很容易做到这一点。
for inputs, targets in validation_dataloader:
predictions = model(inputs)
# Gather all predictions and targets
all_predictions = accelerator.gather(predictions)
all_targets = accelerator.gather(targets)
# Example of use with a *Datasets.Metric*
metric.add_batch(all_predictions, all_targets)
与训练dataloader数据加载器一样,通过 prepare() 传递您的验证dataloader数据加载器可能会改变它:如果您在 X 个 GPU 上运行,它的长度将除以 X(因为您的实际批量大小将乘以 X),除非您设置 split_batches =真。
任何使用您的dataloader训练数据加载器长度的指令(例如,如果您需要总训练步骤数来创建学习率调度程序)应该在调用 prepare() 之后进行。
gather() 方法要求张量在每个进程上都具有相同的大小。 如果您在每个进程上都有不同大小的张量(例如,当动态填充到批处理中的最大长度时),您应该使用 pad_across_processes() 方法将您的张量填充到跨进程的最大大小。
7.Launching your distributed script运行分布式脚本
您可以使用常规命令来启动分布式训练(例如 PyTorch 的 torch.distributed.launch),它们与 Accelerate 完全兼容。 这里唯一需要注意的是,Accelerate 使用环境来确定所有有用的信息,因此 torch.distributed.launch 应该与标志 --use_env 一起使用。
Accelerate 还提供了一个统一所有启动器的 CLI 工具,因此您只需记住一个命令。 要使用它,只需运行
accelerate config
在您的机器上并回答提出的问题,将在 Accelerate 的缓存文件夹中保存一个 default_config.yaml 文件。 该缓存文件夹是(优先级降序):
您的环境变量 HF_HOME 的内容以加速为后缀。
如果不存在,你的环境变量 XDG_CACHE_HOME 的内容后缀为 huggingface/accelerate。
如果这也不存在,文件夹 ~/.cache/huggingface/accelerate
还可以使用标志 --config_file 指定要保存的文件的位置。
完成此操作后,可以通过运行来测试您的设置是否一切顺利
accelerate test
这将启动一个简短的脚本来测试分布式环境。 如果运行良好,您就可以进行下一步了! 请注意,如果您在上一步中指定了配置文件的位置,则还需要在此处传递它:
accelerate test --config_file path_to_config.yaml
做完这些之后,可以用下面的命令运行脚本
accelerate launch path_to_script.py --args_for_the_script
如果你将config 文件存贮在一个non-default位置,你需要想启动器指明,指明方式如下:
accelerate launch --config_file path_to_config.yaml path_to_script.py --args_for_the_script
您还可以覆盖由配置文件确定的任何参数,请参阅 TODO: insert ref here。
8.Launching training from a notebook从笔记本启动训练
在 Accelerate 0.3.0 中,引入了新的 notebook_launcher() 来帮助您从笔记本启动训练功能。 此启动器支持在 Colab 或 Kaggle 上使用 TPU 启动训练,以及在多个 GPU 上进行训练(如果您运行 notebook 的机器上有它们)。
只需在笔记本的一个单元格中定义一个负责您的整个训练和/或评估的函数,然后使用以下代码执行一个单元格:
from accelerate import notebook_launcher
notebook_launcher(training_function)
9.Training on TPU在TPU上训练
如果您想在 TPU 上启动脚本,您应该注意一些注意事项。 在幕后,TPU 将创建一个图表,其中包含您的训练步骤(前向传递、后向传递和优化器步骤)中发生的所有操作。 这就是为什么您的训练的第一步总是很长,因为构建和编译此图以进行优化需要一些时间。
好消息是此编译将被缓存,因此第二步和以下所有步骤将更快。 最重要的消息是,它仅适用于您的所有步骤都执行完全相同的操作,这意味着:
在所有长度中具有相同长度的所有张量
具有静态代码(即,不是长度可能会随着步骤而变化的 for 循环)
在两个步骤之间更改上述任何内容将触发新的编译,这将再次花费大量时间。 在实践中,这意味着您必须特别注意使输入中的所有张量具有相同的形状(例如,如果您遇到 NLP 问题,则没有动态填充)并且不应该使用具有不同长度的层和不同长度的循环。在输入(例如 LSTM)上,否则训练将非常缓慢。 要在脚本中为 TPU 引入特殊行为,您可以检查加速器的 Distributed_type:
from accelerate import DistributedType
if accelerator.distributed_type == DistributedType.TPU:
# do something of static shape
else:
# go crazy and be dynamic
NLP 示例展示了动态填充情况下的示例
最后一件要密切注意的事:如果您的模型具有绑定权重(例如将嵌入矩阵的权重与解码器的权重绑定的语言模型),则将此模型移动到 TPU(您自己或在您通过 模型prepare())将打破捆绑。 之后您需要重新系上权重。 您可以在 Transformers 存储库的 run_clm_no_trainer 脚本中找到一个示例。
10.Other caveats其它注意事项
我们在此处列出了您在脚本转换中可能遇到的所有小问题以及如何解决这些问题。
(1)仅在一个进程上执行语句
您的一些指令只需要在给定服务器上为一个进程运行:例如数据下载或日志语句。 为此,请将语句包装在这样的测试中:
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)
每台机器的/local:如果您在两台具有多个 GPU 的服务器上运行训练,则该指令将在每台服务器上执行一次。 例如,如果您只需要为所有进程(而不是每台机器)执行一次操作,将最终模型上传到模型中心,请将其包装在如下测试中:
if accelerator.is_main_process:
# Is executed once only
对于只希望每台机器执行一次的打印语句,您可以将 print 函数替换为accelerate.print。
(2)Defer execution推迟执行
当您运行通常的脚本时,指令会按顺序执行。 使用 Accelerate 同时在多个 GPU 上部署您的脚本会引入一个复杂性:虽然每个进程按顺序执行所有指令,但有些可能比其他进程更快。
在执行给定指令之前,您可能需要等待所有进程都达到某个点。 例如,在确保每个过程都经过训练之前,您不应该保存模型。 为此,只需在代码中编写以下行:
accelerator.wait_for_everyone()
该指令将阻止所有首先到达它们的进程,直到所有其他进程都到达该点(如果您只在一个 GPU 或 CPU 上运行脚本,这不会做任何事情)。
(3)Saving/loading a model保存/下载模型
保存您训练的模型可能需要做一些调整:首先,您应该等待所有进程到达脚本中如上所示的那个点;然后,您应该在保存模型之前打开模型。 这是因为在执行 prepare() 方法时,您的模型可能已放置在一个更大的模型中,该模型处理分布式训练。 这反过来意味着在不采取任何预防措施的情况下保存模型状态字典将考虑到潜在的额外层,最终您将获得无法在基本模型中加载回的权重。 这就是为什么建议先打开模型的原因。 这是一个例子:
accelerator.wait_for_everyone()
unwrapped_model = accelerator.unwrap_model(model)
accelerator.save(unwrapped_model.state_dict(), filename)
如果您的脚本包含加载检查点的逻辑,我们还建议您在展开的模型中加载权重(这仅在使模型通过 prepare() 后使用加载函数时才有用)。 这是一个例子:
unwrapped_model = accelerator.unwrap_model(model)
unwrapped_model.load_state_dict(torch.load(filename))
请注意,由于所有模型参数都是对张量的引用,因此这会将您的权重加载到模型中。
(4)Gradient clipping梯度裁剪
如果在你的脚本中使用到梯度裁剪,你应该将torch.nn.utils.clip_grad_norm_
or torch.nn.utils.clip_grad_value_的调用分别替换为accelerator.clip_grad_norm_
and accelerator.clip_grad_value_
(5)
Mixed Precision training混合精度训练
如果您在Accelerate 中运行混合精度运行训练,您将根据您的损失获得最佳结果,其中损失是在模型内部计算(例如在 Transformer 模型中)。 模型之外的每个计算都将以全精度执行(这通常是您想要的损失计算,特别是如果它涉及 softmax)。 但是,您可能希望将损失计算放在 Accelerator.autocast 上下文管理器中:
with accelerator.autocast():
loss = complex_loss_function(outputs, target):
关于混合精度训练另一个需要注意的是,梯度会在开始有时会在中间跳过一些更新:由于动态损失缩放策略,在训练期间存在梯度溢出的点,损失缩放因子减少到以避免在下一步再次发生这种情况。
这意味着您可以在没有更新时更新您的学习率调度程序,这在通常情况下很好,但当您的训练数据非常少时或者如果您的调度程序的第一个学习率值非常重要时,可能会产生影响。 在这种情况下,您可以在优化器步骤未完成时跳过学习率调度程序更新,如下所示:
if not accelerator.optimizer_step_was_skipped:
lr_scheduler.step()
(6)DeepSpeed深速
DeepSpeed 支持是实验性的,因此底层 API 将在不久的将来发展,可能会有一些轻微的重大变化。 特别是,Accelerate 不支持您自己编写的 DeepSpeed 配置,这将在下一个版本中添加。 DeepSpeed 集成的一个主要注意事项是 DeepSpeed 启动器始终将 local_rank 变量传递给训练脚本,因此您的训练脚本应该接受它(无论您是使用 DeepSpeed 启动器启动训练还是加速启动)。
notebook_launcher()notebook_launcher()还不支持DeepSpeed集成
(7) Internal mechanism内部机制
在内部,该库的启动首先分析脚本启动所在的环境,以确定使用哪种分布式设置、有多少不同的进程以及当前脚本在哪个进程中。所有这些信息都存储在 ~AcceleratorState .
此类在您第一次实例化notebook_launcher()以及执行分布式设置所需的任何特定初始化时被初始化。然后,它的状态将通过 AcceleratorState 的所有实例唯一共享。
然后,当调用 prepare() 时,库:
将容器内的模型进行打包以用于分布式设置;
在AcceleratedOptimizer对优化器打包;
在DataLoaderShard 中创建新版本的dataloader数据加载器。
虽然模型和优化器只是放在简单的包装器中,但重新创建了dataloader数据加载器。这主要是因为 PyTorch 不允许用户在创建dataloader数据加载器后更改它的 batch_sampler,并且库通过更改该 batch_sampler 来处理进程之间的数据分片以产生每隔 num_processes 个批次。
DataLoaderShard的子类DataLoader添加如下功能:
它在每次新迭代时同步所有进程的适当随机数生成器,以确保在进程之间任何随机化(如洗牌)以完全相同的方式完成。
它在产生它们之前将batcher放在正确的设备上(除非您选择退出 device_placement=True)。
随机数生成器同步将默认同步:
PyTorch >= 1.6 的给定采样器(如 PyTorch RandomSampler)的生成器属性
PyTorch <=1.5.1 中的主要随机数生成器
可以选择与主Accelerator的rng_types参数同步的随机数生成器,在 PyTorch >= 1.6 中,建议依赖本地生成器,以避免在所有进程中在主随机数生成器中设置相同的种子。
同步主torch(或 CUDA 或 XLA)随机数生成器将影响您在数据集中可能拥有的任何其他潜在随机工件(如随机数据增强),因为所有进程都会从torch随机模块中获得相同的随机数(所以 如果它由torch控制,将应用相同的随机数据增强)。
自定义采样器、批处理采样器或可迭代数据集的随机化部分应使用本地 torch.Generator 对象(在 PyTorch >= 1.6 中)完成,请参阅传统的 RandomSampler.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。