Accelerate 文档

在TPU上训练

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

在 TPU 上训练

即使使用 Accelerate,在 TPU 上训练也可能与在多 GPU 上训练略有不同。本指南旨在向您展示您应该注意的地方以及原因,以及一般的最佳实践。

在 Notebook 中训练

在 TPU 上训练时,主要的注意点来自 notebook_launcher()。正如 notebook 教程中提到的,您需要将训练代码重构为一个可以传递给 notebook_launcher() 函数的函数,并注意不要在 GPU 上声明任何张量。

虽然在 TPU 上,最后一部分没有那么重要,但需要理解的关键部分是,当您从 notebook 启动代码时,您是通过一个名为 forking 的过程来实现的。当从命令行启动时,您执行 spawning,此时 python 进程当前未运行,您在其中 spawn 一个新进程。由于您的 Jupyter notebook 已经在使用 python 进程,因此您需要从中 fork 一个新进程来启动代码。

重要的是关于声明模型。在 forked TPU 进程上,建议您实例化模型一次,并将其传递到您的训练函数中。这与在 GPU 上训练不同,在 GPU 上,您创建 n 个模型,这些模型的梯度在某些时刻同步和反向传播。相反,一个模型实例在所有节点之间共享,并在它们之间来回传递。这在低资源 TPU 上训练时尤其重要,例如 Kaggle kernels 或 Google Colaboratory 中提供的那些。

下面是一个训练函数的示例,如果要在 CPU 或 GPU 上训练,则将其传递给 notebook_launcher()

此代码片段基于 simple_nlp_example notebook 中的代码,可以在 这里找到,并为了简单起见进行了一些修改

def training_function():
    # Initialize accelerator
    accelerator = Accelerator()
    model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", num_labels=2)
    train_dataloader, eval_dataloader = create_dataloaders(
        train_batch_size=hyperparameters["train_batch_size"], eval_batch_size=hyperparameters["eval_batch_size"]
    )

    # Instantiate optimizer
    optimizer = AdamW(params=model.parameters(), lr=hyperparameters["learning_rate"])

    # Prepare everything
    # There is no specific order to remember, we just need to unpack the objects in the same order we gave them to the
    # prepare method.
    model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
        model, optimizer, train_dataloader, eval_dataloader
    )

    num_epochs = hyperparameters["num_epochs"]
    # Now we train the model
    for epoch in range(num_epochs):
        model.train()
        for step, batch in enumerate(train_dataloader):
            outputs = model(**batch)
            loss = outputs.loss
            accelerator.backward(loss)

            optimizer.step()
            optimizer.zero_grad()
from accelerate import notebook_launcher

notebook_launcher(training_function)

如果 Accelerate 已配置为 TPU,则 notebook_launcher 将默认为 8 个进程

如果您使用此示例并在训练循环内部声明模型,那么在低资源系统上,您可能会看到类似以下的错误

ProcessExitedException: process 0 terminated with signal SIGSEGV

此错误非常隐晦,但基本解释是您耗尽了系统 RAM。您可以通过重新配置训练函数以接受单个 model 参数并在外部单元格中声明它来完全避免这种情况

# In another Jupyter cell
model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", num_labels=2)
+ def training_function(model):
      # Initialize accelerator
      accelerator = Accelerator()
-     model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", num_labels=2)
      train_dataloader, eval_dataloader = create_dataloaders(
          train_batch_size=hyperparameters["train_batch_size"], eval_batch_size=hyperparameters["eval_batch_size"]
      )
  ...

最后使用以下方式调用训练函数

  from accelerate import notebook_launcher
- notebook_launcher(training_function)
+ notebook_launcher(training_function, (model,))

上述解决方法仅在从 Google Colaboratory 或 Kaggle 等低资源服务器上的 Jupyter Notebook 启动 TPU 实例时才需要。如果使用脚本或在更强大的服务器上启动,则不需要预先声明模型。

混合精度和全局变量

正如混合精度教程中提到的,Accelerate 支持 fp16 和 bf16,两者都可以在 TPU 上使用。话虽如此,理想情况下应使用 bf16,因为它使用起来非常高效。

在 TPU 上使用 bf16 和 Accelerate 时,有两个“层”:基础层和操作层。

在基础层,当将 mixed_precision="bf16" 传递给 Accelerator 时,将启用此功能,例如

accelerator = Accelerator(mixed_precision="bf16")

默认情况下,这会将 TPU 上的 torch.floattorch.double 转换为 bfloat16。设置的具体配置是将环境变量 XLA_USE_BF16 设置为 1

您可以执行进一步的配置,即设置 XLA_DOWNCAST_BF16 环境变量。如果设置为 1,则 torch.floatbfloat16torch.doublefloat32

当传递 downcast_bf16=True 时,将在 Accelerator 对象中执行此操作

accelerator = Accelerator(mixed_precision="bf16", downcast_bf16=True)

当您尝试计算指标、记录值以及原始 bf16 张量将无法使用的情况下,使用降级而不是处处使用 bf16 是有益的。

TPU 上的训练时间

当您启动脚本时,您可能会注意到训练起初似乎异常缓慢。这是因为 TPU 首先运行几个批次的数据,以查看要分配多少内存,然后再最终极其高效地利用此配置的内存分配。

如果您注意到由于使用了更大的批次大小,用于计算模型指标的评估代码花费的时间更长,建议在评估代码太慢的情况下,保持批次大小与训练数据相同。否则,内存将在前几次迭代后重新分配到这个新的批次大小。

仅仅因为内存被分配了,并不意味着它会被使用,或者当返回到您的训练数据加载器时,批次大小会增加。

< > 在 GitHub 上更新