Hugging Face在PyTorch / XLA TPUs上的应用
Hugging Face在PyTorch/XLA TPUs上的应用
使用PyTorch / XLA在云TPUs上训练您喜爱的Transformers模型
PyTorch-TPU项目是Facebook PyTorch团队和Google TPU团队的合作项目,于2019年PyTorch开发者大会正式推出。此后,我们与Hugging Face团队合作,为使用PyTorch / XLA在云TPUs上训练提供了一流的支持。这种新的集成使PyTorch用户能够在云TPUs上运行和扩展其模型,同时保持与Hugging Face训练器接口完全一致。
本博文提供了Hugging Face库中所做更改的概述,PyTorch / XLA库的功能,一个用于在云TPUs上训练您喜爱的Transformers模型的示例,以及一些性能基准。如果您迫不及待地想要开始使用TPUs,请直接跳到“在云TPUs上训练您的Transformer”部分 – 我们在Trainer
模块中为您处理所有的PyTorch / XLA机制!
XLA:TPU设备类型
PyTorch / XLA为PyTorch添加了一个新的xla
设备类型。这个设备类型的使用方式与其他PyTorch设备类型相同。例如,下面是如何创建和打印一个XLA张量:
- 使用Huggingface Transformers和Ray进行检索增强生成
- Hugging Face 阅读,2021年2月 – 长程 Transformer
- 在Hugging Face中使用🤗 Transformers对Wav2Vec2进行英语ASR的微调
import torch
import torch_xla
import torch_xla.core.xla_model as xm
t = torch.randn(2, 2, device=xm.xla_device())
print(t.device)
print(t)
这段代码应该很熟悉。PyTorch / XLA使用与普通PyTorch相同的接口,只是增加了一些功能。导入torch_xla
会初始化PyTorch / XLA,xm.xla_device()
会返回当前的XLA设备。这可能是CPU、GPU或TPU,取决于您的环境,但在本博文中,我们主要关注TPU。
Trainer
模块利用一个TrainingArguments
数据类来定义训练的具体参数。它处理多个参数,包括批量大小、学习率、梯度累积等等,以及所使用的设备。基于上述情况,在使用XLA:TPU设备时,在TrainingArguments._setup_devices()
中,我们只需返回要由Trainer
使用的TPU设备:
@dataclass
class TrainingArguments:
...
@cached_property
@torch_required
def _setup_devices(self) -> Tuple["torch.device", int]:
...
elif is_torch_tpu_available():
device = xm.xla_device()
n_gpu = 0
...
return device, n_gpu
XLA设备步骤计算
在典型的XLA:TPU训练场景中,我们在多个TPU核心上并行训练(一个云TPU设备包含8个TPU核心)。因此,我们需要确保所有梯度在数据并行副本之间进行交换,通过合并梯度并执行优化器步骤。为此,我们提供了xm.optimizer_step(optimizer)
,它执行梯度合并和步骤执行。在Hugging Face训练器中,我们相应地更新训练步骤,使用PyTorch / XLA的API:
class Trainer:
…
def train(self, *args, **kwargs):
...
if is_torch_tpu_available():
xm.optimizer_step(self.optimizer)
PyTorch / XLA输入管道
运行PyTorch / XLA模型有两个主要部分:(1)追踪和延迟执行模型的图(请参阅下面的“PyTorch / XLA库”部分,了解更深入的解释)和(2)提供输入给模型。如果没有任何优化,模型的追踪/执行和输入提供将被串行执行,导致主机CPU和TPU加速器在执行过程中都会处于空闲状态。为了避免这种情况,我们提供了一个API,将这两个过程进行流水线处理,从而能够在执行步骤n时重叠步骤n+1的追踪。
import torch_xla.distributed.parallel_loader as pl
...
dataloader = pl.MpDeviceLoader(dataloader, device)
检查点的写入和加载
当从 XLA 设备中对张量进行检查点操作,并从检查点加载时,它将被加载回原始设备。在对模型的张量进行检查点操作之前,你需要确保所有的张量都在 CPU 设备上,而不是 XLA 设备上。这样,当你加载张量时,你将通过 CPU 设备加载它们,然后有机会将它们放置在你想要的任何 XLA 设备上。我们提供了 xm.save()
API 来实现这一点,它已经能够确保只有一个进程(在每个主机上)或一个全局进程(如果使用跨主机共享文件系统)将数据写入存储位置。
class PreTrainedModel(nn.Module, ModuleUtilsMixin, GenerationMixin):
…
def save_pretrained(self, save_directory):
...
if getattr(self.config, "xla_device", False):
import torch_xla.core.xla_model as xm
if xm.is_master_ordinal():
# 保存配置文件
model_to_save.config.save_pretrained(save_directory)
# xm.save 负责只从主进程保存
xm.save(state_dict, output_model_file)
class Trainer:
…
def train(self, *args, **kwargs):
...
if is_torch_tpu_available():
xm.rendezvous("saving_optimizer_states")
xm.save(self.optimizer.state_dict(),
os.path.join(output_dir, "optimizer.pt"))
xm.save(self.lr_scheduler.state_dict(),
os.path.join(output_dir, "scheduler.pt"))
PyTorch / XLA 库
PyTorch / XLA 是一个使用 XLA 线性代数编译器将 PyTorch 深度学习框架与 XLA 设备(包括 CPU、GPU 和云 TPU)连接起来的 Python 包。以下部分的内容也可以在我们的 API_GUIDE.md 中找到。
PyTorch / XLA 张量是惰性的
使用 XLA 张量和设备只需要改变几行代码。但是,尽管 XLA 张量的行为很像 CPU 和 CUDA 张量,但它们的内部实现是不同的。CPU 和 CUDA 张量会立即或急切地执行操作。而 XLA 张量则是惰性的。它们会将操作记录在图中,直到需要结果为止。这种延迟执行的方式让 XLA 优化操作。多个独立操作的图可能会被融合成一个优化的操作。
惰性执行对调用者来说通常是透明的。PyTorch / XLA 会自动构建图,并将它们发送到 XLA 设备,并在 XLA 设备和 CPU 之间复制数据时进行同步。在进行优化步骤时插入一个屏障可以显式地同步 CPU 和 XLA 设备。
这意味着当你调用 model(input)
进行前向传播、计算损失 loss.backward()
,以及进行优化步骤 xm.optimizer_step(optimizer)
时,所有操作的图都在后台构建。只有当你明确地评估张量(例如打印张量或将其移动到 CPU 设备)或标记一个步骤(每次迭代通过 MpDeviceLoader
时都会自动完成)时,才会执行完整的步骤。
追踪、编译、执行和重复
从用户的角度来看,运行在 PyTorch / XLA 上的模型的典型训练过程包括前向传播、反向传播和优化步骤。从 PyTorch / XLA 库的角度来看,情况有所不同。
当用户运行前向和后向传播时,会实时跟踪中间表示(IR)图。可以如下所示检查导致每个根/输出张量的 IR 图:
>>> import torch
>>> import torch_xla
>>> import torch_xla.core.xla_model as xm
>>> t = torch.tensor(1, device=xm.xla_device())
>>> s = t*t
>>> print(torch_xla._XLAC._get_xla_tensors_text([s]))
IR {
%0 = s64[] prim::Constant(), value=1
%1 = s64[] prim::Constant(), value=0
%2 = s64[] xla::as_strided_view_update(%1, %0), size=(), stride=(), storage_offset=0
%3 = s64[] aten::as_strided(%2), size=(), stride=(), storage_offset=0
%4 = s64[] aten::mul(%3, %3), ROOT=0
}
在用户程序上运行前向和后向传递时,该实时图会累积,并且一旦调用xm.mark_step()
(由pl.MpDeviceLoader
间接调用),实时张量的图就会被截断。这个截断标志着一个步骤的完成,随后我们将IR图降低到XLA Higher Level Operations(HLO),这是XLA的IR语言。
然后,将此HLO图编译成TPU二进制文件,并在TPU设备上执行。然而,这个编译步骤可能代价高昂,通常比单个步骤花费的时间长,因此如果我们每个步骤都编译用户的程序,开销将很高。为了避免这种情况,我们有一个缓存,通过其HLO图的唯一哈希标识符来存储编译的TPU二进制文件。因此,一旦在第一步骤中填充了此TPU二进制文件缓存,随后的步骤通常不需要重新编译新的TPU二进制文件;而是可以直接从缓存中查找所需的二进制文件。
由于TPU编译通常比步骤执行时间慢得多,这意味着如果图形的形状保持变化,我们将会有缓存未命中并且编译过于频繁。为了最小化编译成本,我们建议尽可能保持张量形状静态。Hugging Face库的形状在很大程度上已经是静态的,输入令牌会被适当地填充,因此在整个训练过程中,缓存应该会一直被命中。可以使用PyTorch / XLA提供的调试工具来检查这一点。在下面的示例中,您可以看到编译仅发生了5次(CompileTime
),而执行发生在1220个步骤中的每一个(ExecuteTime
):
>>> import torch_xla.debug.metrics as met
>>> print(met.metrics_report())
Metric: CompileTime
TotalSamples: 5
Accumulator: 28s920ms153.731us
ValueRate: 092ms152.037us / second
Rate: 0.0165028 / second
Percentiles: 1%=428ms053.505us; 5%=428ms053.505us; 10%=428ms053.505us; 20%=03s640ms888.060us; 50%=03s650ms126.150us; 80%=11s110ms545.595us; 90%=11s110ms545.595us; 95%=11s110ms545.595us; 99%=11s110ms545.595us
Metric: DeviceLockWait
TotalSamples: 1281
Accumulator: 38s195ms476.007us
ValueRate: 151ms051.277us / second
Rate: 4.54374 / second
Percentiles: 1%=002.895us; 5%=002.989us; 10%=003.094us; 20%=003.243us; 50%=003.654us; 80%=038ms978.659us; 90%=192ms495.718us; 95%=208ms893.403us; 99%=221ms394.520us
Metric: ExecuteTime
TotalSamples: 1220
Accumulator: 04m22s555ms668.071us
ValueRate: 923ms872.877us / second
Rate: 4.33049 / second
Percentiles: 1%=045ms041.018us; 5%=213ms379.757us; 10%=215ms434.912us; 20%=217ms036.764us; 50%=219ms206.894us; 80%=222ms335.146us; 90%=227ms592.924us; 95%=231ms814.500us; 99%=239ms691.472us
Counter: CachedCompile
Value: 1215
Counter: CreateCompileHandles
Value: 5
...
在云TPU上训练您的Transformer
要配置您的虚拟机和云TPU,请按照“设置计算引擎实例”和“启动云TPU资源”(写作时的pytorch-1.7版本)部分。一旦您创建了虚拟机和云TPU,使用它们就像SSH到您的GCE虚拟机并运行以下命令来启动bert-large-uncased
训练(批量大小是为v3-8设备,可能会在v2-8上OOM):
conda activate torch-xla-1.7
export TPU_IP_ADDRESS="输入你的TPU IP地址" # 例如 10.0.0.2
export XRT_TPU_CONFIG="tpu_worker;0;$TPU_IP_ADDRESS:8470"
git clone -b v4.2.2 https://github.com/huggingface/transformers.git
cd transformers && pip install .
pip install datasets==1.2.1
python examples/xla_spawn.py \
--num_cores 8 \
examples/language-modeling/run_mlm.py \
--dataset_name wikitext \
--dataset_config_name wikitext-103-raw-v1 \
--max_seq_length 512 \
--pad_to_max_length \
--logging_dir ./tensorboard-metrics \
--cache_dir ./cache_dir \
--do_train \
--do_eval \
--overwrite_output_dir \
--output_dir language-modeling \
--overwrite_cache \
--tpu_metrics_debug \
--model_name_or_path bert-large-uncased \
--num_train_epochs 3 \
--per_device_train_batch_size 8 \
--per_device_eval_batch_size 8 \
--save_steps 500000
以上代码将在大约不到200分钟的时间内完成训练,并得到一个评估困惑度约为3.25的结果。
性能基准测试
以下表格显示在一个包含4个TPU v3芯片的v3-8云TPU系统上运行PyTorch / XLA训练bert-large-uncased的性能。用于所有基准测试测量的数据集是WikiText103数据集,我们使用Hugging Face examples中提供的run_mlm.py脚本。为了确保工作负载不受主机CPU限制,我们在这些测试中使用n1-standard-96 CPU配置,但您也可以使用较小的配置而不影响性能。
开始使用PyTorch / XLA和TPU
请参阅Hugging Face examples中的“在TPU上运行”部分以开始。要获取有关我们的API的更详细描述,请查看我们的API指南,并且要获取性能最佳实践,请查看我们的故障排除指南。对于通用的PyTorch / XLA示例,请运行我们提供的带有免费云TPU访问权限的以下Colab笔记本。要直接在GCP上运行,请参阅我们文档网站上标记为“PyTorch”的教程。
有其他问题或疑问吗?请在https://github.com/huggingface/transformers/issues 或 https://github.com/pytorch/xla/issues 上提出问题或疑问。