Transformers--4-37-中文文档-四-
Transformers 4.37 中文文档(四)
基准测试
Hugging Face 的基准测试工具已被弃用,建议使用外部基准测试库来衡量 Transformer 模型的速度和内存复杂性。
让我们看看如何对🤗 Transformers 模型进行基准测试,最佳实践以及已有的基准测试。
可以在此处找到更详细解释如何对🤗 Transformers 模型进行基准测试的笔记本。
如何对🤗 Transformers 模型进行基准测试
PyTorchBenchmark
和TensorFlowBenchmark
类允许灵活地对🤗 Transformers 模型进行基准测试。基准类允许我们测量峰值内存使用量和所需时间,用于推理和训练。
在此,推理由单个前向传递定义,训练由单个前向传递和反向传递定义。
基准类PyTorchBenchmark
和TensorFlowBenchmark
分别期望使用PyTorchBenchmarkArguments
和TensorFlowBenchmarkArguments
类型的对象进行实例化。PyTorchBenchmarkArguments
和TensorFlowBenchmarkArguments
是数据类,包含其对应基准类的所有相关配置。在以下示例中,展示了如何对类型为bert-base-cased的 BERT 模型进行基准测试。
Pytorch 隐藏了 Pytorch 内容
>>> from transformers import PyTorchBenchmark, PyTorchBenchmarkArguments
>>> args = PyTorchBenchmarkArguments(models=["bert-base-uncased"], batch_sizes=[8], sequence_lengths=[8, 32, 128, 512])
>>> benchmark = PyTorchBenchmark(args)
TensorFlow 隐藏了 TensorFlow 内容
>>> from transformers import TensorFlowBenchmark, TensorFlowBenchmarkArguments
>>> args = TensorFlowBenchmarkArguments(
... models=["bert-base-uncased"], batch_sizes=[8], sequence_lengths=[8, 32, 128, 512]
... )
>>> benchmark = TensorFlowBenchmark(args)
在这里,基准参数数据类给出了三个参数,分别是models
、batch_sizes
和sequence_lengths
。参数models
是必需的,期望从model hub中的模型标识符列表。list
参数batch_sizes
和sequence_lengths
定义了对模型进行基准测试的input_ids
的大小。还有许多其他参数可以通过基准参数数据类进行配置。有关这些参数的更多详细信息,可以直接查阅文件src/transformers/benchmark/benchmark_args_utils.py
、src/transformers/benchmark/benchmark_args.py
(用于 PyTorch)和src/transformers/benchmark/benchmark_args_tf.py
(用于 Tensorflow)。或者,从根目录运行以下 shell 命令将分别打印出 PyTorch 和 Tensorflow 的所有可配置参数的描述性列表。
Pytorch 隐藏了 Pytorch 内容
python examples/pytorch/benchmarking/run_benchmark.py --help
然后,通过调用benchmark.run()
来简单运行实例化的基准对象。
>>> results = benchmark.run()
>>> print(results)
==================== INFERENCE - SPEED - RESULT ====================
--------------------------------------------------------------------------------
Model Name Batch Size Seq Length Time in s
--------------------------------------------------------------------------------
bert-base-uncased 8 8 0.006
bert-base-uncased 8 32 0.006
bert-base-uncased 8 128 0.018
bert-base-uncased 8 512 0.088
--------------------------------------------------------------------------------
==================== INFERENCE - MEMORY - RESULT ====================
--------------------------------------------------------------------------------
Model Name Batch Size Seq Length Memory in MB
--------------------------------------------------------------------------------
bert-base-uncased 8 8 1227
bert-base-uncased 8 32 1281
bert-base-uncased 8 128 1307
bert-base-uncased 8 512 1539
--------------------------------------------------------------------------------
==================== ENVIRONMENT INFORMATION ====================
- transformers_version: 2.11.0
- framework: PyTorch
- use_torchscript: False
- framework_version: 1.4.0
- python_version: 3.6.10
- system: Linux
- cpu: x86_64
- architecture: 64bit
- date: 2020-06-29
- time: 08:58:43.371351
- fp16: False
- use_multiprocessing: True
- only_pretrain_model: False
- cpu_ram_mb: 32088
- use_gpu: True
- num_gpus: 1
- gpu: TITAN RTX
- gpu_ram_mb: 24217
- gpu_power_watts: 280.0
- gpu_performance_state: 2
- use_tpu: False
TensorFlow 隐藏了 TensorFlow 内容
python examples/tensorflow/benchmarking/run_benchmark_tf.py --help
然后,通过调用benchmark.run()
来简单运行实例化的基准对象。
>>> results = benchmark.run()
>>> print(results)
>>> results = benchmark.run()
>>> print(results)
==================== INFERENCE - SPEED - RESULT ====================
--------------------------------------------------------------------------------
Model Name Batch Size Seq Length Time in s
--------------------------------------------------------------------------------
bert-base-uncased 8 8 0.005
bert-base-uncased 8 32 0.008
bert-base-uncased 8 128 0.022
bert-base-uncased 8 512 0.105
--------------------------------------------------------------------------------
==================== INFERENCE - MEMORY - RESULT ====================
--------------------------------------------------------------------------------
Model Name Batch Size Seq Length Memory in MB
--------------------------------------------------------------------------------
bert-base-uncased 8 8 1330
bert-base-uncased 8 32 1330
bert-base-uncased 8 128 1330
bert-base-uncased 8 512 1770
--------------------------------------------------------------------------------
==================== ENVIRONMENT INFORMATION ====================
- transformers_version: 2.11.0
- framework: Tensorflow
- use_xla: False
- framework_version: 2.2.0
- python_version: 3.6.10
- system: Linux
- cpu: x86_64
- architecture: 64bit
- date: 2020-06-29
- time: 09:26:35.617317
- fp16: False
- use_multiprocessing: True
- only_pretrain_model: False
- cpu_ram_mb: 32088
- use_gpu: True
- num_gpus: 1
- gpu: TITAN RTX
- gpu_ram_mb: 24217
- gpu_power_watts: 280.0
- gpu_performance_state: 2
- use_tpu: False
默认情况下,对推理的时间和所需内存进行基准测试。在上面的示例输出中,前两个部分显示了与推理时间和推理内存对应的结果。此外,关于计算环境的所有相关信息,例如 GPU 类型、系统、库版本等,都会在第三部分的环境信息下打印出来。当向PyTorchBenchmarkArguments
和TensorFlowBenchmarkArguments
分别添加参数save_to_csv=True
时,此信息可以选择保存在.csv文件中。在这种情况下,每个部分都保存在单独的.csv文件中。可以通过参数数据类可选地定义每个.csv文件的路径。
用户可以选择通过其模型标识符,例如 bert-base-uncased
,对预训练模型进行基准测试,也可以选择对任何可用模型类的任意配置进行基准测试。在这种情况下,必须在基准参数中插入一组配置列表。如下所示。
Pytorch 隐藏了 Pytorch 内容
>>> from transformers import PyTorchBenchmark, PyTorchBenchmarkArguments, BertConfig
>>> args = PyTorchBenchmarkArguments(
... models=["bert-base", "bert-384-hid", "bert-6-lay"], batch_sizes=[8], sequence_lengths=[8, 32, 128, 512]
... )
>>> config_base = BertConfig()
>>> config_384_hid = BertConfig(hidden_size=384)
>>> config_6_lay = BertConfig(num_hidden_layers=6)
>>> benchmark = PyTorchBenchmark(args, configs=[config_base, config_384_hid, config_6_lay])
>>> benchmark.run()
==================== INFERENCE - SPEED - RESULT ====================
--------------------------------------------------------------------------------
Model Name Batch Size Seq Length Time in s
--------------------------------------------------------------------------------
bert-base 8 128 0.006
bert-base 8 512 0.006
bert-base 8 128 0.018
bert-base 8 512 0.088
bert-384-hid 8 8 0.006
bert-384-hid 8 32 0.006
bert-384-hid 8 128 0.011
bert-384-hid 8 512 0.054
bert-6-lay 8 8 0.003
bert-6-lay 8 32 0.004
bert-6-lay 8 128 0.009
bert-6-lay 8 512 0.044
--------------------------------------------------------------------------------
==================== INFERENCE - MEMORY - RESULT ====================
--------------------------------------------------------------------------------
Model Name Batch Size Seq Length Memory in MB
--------------------------------------------------------------------------------
bert-base 8 8 1277
bert-base 8 32 1281
bert-base 8 128 1307
bert-base 8 512 1539
bert-384-hid 8 8 1005
bert-384-hid 8 32 1027
bert-384-hid 8 128 1035
bert-384-hid 8 512 1255
bert-6-lay 8 8 1097
bert-6-lay 8 32 1101
bert-6-lay 8 128 1127
bert-6-lay 8 512 1359
--------------------------------------------------------------------------------
==================== ENVIRONMENT INFORMATION ====================
- transformers_version: 2.11.0
- framework: PyTorch
- use_torchscript: False
- framework_version: 1.4.0
- python_version: 3.6.10
- system: Linux
- cpu: x86_64
- architecture: 64bit
- date: 2020-06-29
- time: 09:35:25.143267
- fp16: False
- use_multiprocessing: True
- only_pretrain_model: False
- cpu_ram_mb: 32088
- use_gpu: True
- num_gpus: 1
- gpu: TITAN RTX
- gpu_ram_mb: 24217
- gpu_power_watts: 280.0
- gpu_performance_state: 2
- use_tpu: False
TensorFlow 隐藏了 TensorFlow 内容
>>> from transformers import TensorFlowBenchmark, TensorFlowBenchmarkArguments, BertConfig
>>> args = TensorFlowBenchmarkArguments(
... models=["bert-base", "bert-384-hid", "bert-6-lay"], batch_sizes=[8], sequence_lengths=[8, 32, 128, 512]
... )
>>> config_base = BertConfig()
>>> config_384_hid = BertConfig(hidden_size=384)
>>> config_6_lay = BertConfig(num_hidden_layers=6)
>>> benchmark = TensorFlowBenchmark(args, configs=[config_base, config_384_hid, config_6_lay])
>>> benchmark.run()
==================== INFERENCE - SPEED - RESULT ====================
--------------------------------------------------------------------------------
Model Name Batch Size Seq Length Time in s
--------------------------------------------------------------------------------
bert-base 8 8 0.005
bert-base 8 32 0.008
bert-base 8 128 0.022
bert-base 8 512 0.106
bert-384-hid 8 8 0.005
bert-384-hid 8 32 0.007
bert-384-hid 8 128 0.018
bert-384-hid 8 512 0.064
bert-6-lay 8 8 0.002
bert-6-lay 8 32 0.003
bert-6-lay 8 128 0.0011
bert-6-lay 8 512 0.074
--------------------------------------------------------------------------------
==================== INFERENCE - MEMORY - RESULT ====================
--------------------------------------------------------------------------------
Model Name Batch Size Seq Length Memory in MB
--------------------------------------------------------------------------------
bert-base 8 8 1330
bert-base 8 32 1330
bert-base 8 128 1330
bert-base 8 512 1770
bert-384-hid 8 8 1330
bert-384-hid 8 32 1330
bert-384-hid 8 128 1330
bert-384-hid 8 512 1540
bert-6-lay 8 8 1330
bert-6-lay 8 32 1330
bert-6-lay 8 128 1330
bert-6-lay 8 512 1540
--------------------------------------------------------------------------------
==================== ENVIRONMENT INFORMATION ====================
- transformers_version: 2.11.0
- framework: Tensorflow
- use_xla: False
- framework_version: 2.2.0
- python_version: 3.6.10
- system: Linux
- cpu: x86_64
- architecture: 64bit
- date: 2020-06-29
- time: 09:38:15.487125
- fp16: False
- use_multiprocessing: True
- only_pretrain_model: False
- cpu_ram_mb: 32088
- use_gpu: True
- num_gpus: 1
- gpu: TITAN RTX
- gpu_ram_mb: 24217
- gpu_power_watts: 280.0
- gpu_performance_state: 2
- use_tpu: False
再次,推理时间和所需内存用于推理的测量,但这次是针对BertModel
类的自定义配置。在决定应该为哪种配置训练模型时,此功能尤其有帮助。
基准测试最佳实践
本节列出了在对模型进行基准测试时应注意的一些最佳实践。
-
目前仅支持单设备基准测试。在 GPU 上进行基准测试时,建议用户通过在 shell 中设置
CUDA_VISIBLE_DEVICES
环境变量来指定代码应在哪个设备上运行,例如在运行代码之前设置export CUDA_VISIBLE_DEVICES=0
。 -
选项
no_multi_processing
应仅在测试和调试时设置为True
。为了确保准确的内存测量,建议通过确保将no_multi_processing
设置为True
来在单独的进程中运行每个内存基准测试。 -
在分享模型基准测试结果时,应始终说明环境信息。由于不同的 GPU 设备、库版本等之间的结果可能会有很大差异,因此单独的基准测试结果对社区来说并不是非常有用。
分享您的基准测试
以前,所有可用的核心模型(当时为 10 个)都已针对推理时间进行了基准测试,涵盖了许多不同的设置:使用 PyTorch,使用 TorchScript 或不使用,使用 TensorFlow,使用 XLA 或不使用。所有这些测试都是在 CPU(除了 TensorFlow XLA)和 GPU 上进行的。
有了新的基准工具,与社区分享基准测试结果比以往更容易
🤗 Transformers 笔记本
您可以在这里找到 Hugging Face 提供的官方笔记本列表。
此外,我们还想在这里列出社区创建的有趣内容。如果您编写了一些利用🤗 Transformers 的笔记本,并希望在此列出,请提交一个 Pull Request,以便将其包含在社区笔记本下。
Hugging Face 的笔记本 🤗
文档笔记
您可以在 Colab 中将文档的任何页面打开为笔记本(这些页面上直接有一个按钮),但如果您需要,它们也在这里列出:
笔记本 | 描述 | ||
---|---|---|---|
库的快速浏览 | Transformers 中各种 API 的介绍 | ||
任务摘要 | 如何逐个任务运行 Transformers 库的模型 | ||
数据预处理 | 如何使用分词器预处理数据 | ||
微调预训练模型 | 如何使用 Trainer 微调预训练模型 | ||
分词器摘要 | 分词器算法之间的差异 | ||
多语言模型 | 如何使用库中的多语言模型 |
PyTorch 示例
自然语言处理
笔记本 | 描述 | ||
---|---|---|---|
训练您的分词器 | 如何训练和使用您自己的分词器 | ||
训练您的语言模型 | 如何轻松开始使用 transformers | ||
如何在文本分类上微调模型 | 展示如何预处理数据并在任何 GLUE 任务上微调预训练模型。 | ||
如何在语言建模上微调模型 | 展示如何预处理数据并在因果或掩码 LM 任务上微调预训练模型。 | ||
如何在标记分类上微调模型 | 展示如何预处理数据并在标记分类任务(NER,PoS)上微调预训练模型。 | ||
如何在问答上微调模型 | 展示如何预处理数据并在 SQUAD 上微调预训练模型 | ||
如何在多项选择上微调模型 | 展示如何预处理数据并在 SWAG 上微调预训练模型 | ||
如何在翻译上微调模型 | 展示如何预处理数据并在 WMT 上微调预训练模型 | ||
如何在摘要上微调模型 | 展示如何预处理数据并在 XSUM 上微调预训练模型 | ||
如何从头开始训练语言模型 | 突出显示有效训练 Transformer 模型在自定义数据上的所有步骤 | ||
如何生成文本 | 如何使用不同的解码方法为 transformers 进行语言生成 | ||
如何生成文本(带约束) | 如何通过用户提供的约束来引导语言生成 | ||
Reformer | Reformer 如何推动语言建模的极限 |
计算机视觉
笔记本 | 描述 | ||
---|---|---|---|
如何在图像分类上微调模型(Torchvision) | 展示如何使用 Torchvision 预处理数据并在图像分类上微调任何预训练的视觉模型 | ||
如何在图像分类上微调模型(Albumentations) | 展示如何使用 Albumentations 预处理数据并在图像分类上微调任何预训练的视觉模型 | ||
如何在图像分类上微调模型(Kornia) | 展示如何使用 Kornia 预处理数据并在图像分类上微调任何预训练的视觉模型 | ||
如何使用 OWL-ViT 进行零样本目标检测 | 展示如何使用文本查询在图像上执行零样本目标检测 | ||
如何微调图像字幕模型 | 展示如何在自定义数据集上微调 BLIP 以进行图像字幕 | ||
如何使用 Transformers 构建图像相似性系统 | 展示如何构建图像相似性系统 | ||
如何在语义分割上微调 SegFormer 模型 | 展示如何预处理数据并在语义分割上微调预训练的 SegFormer 模型 | ||
如何在视频分类上微调 VideoMAE 模型 | 展示如何预处理数据并在视频分类上微调预训练的 VideoMAE 模型 |
音频
笔记本 | 描述 | ||
---|---|---|---|
如何在英语中微调语音识别模型 | 展示如何预处理数据并在 TIMIT 上微调预训练的语音模型 | ||
如何在任何语言中微调语音识别模型 | 展示如何预处理数据并在 Common Voice 上微调多语言预训练语音模型 | ||
如何在音频分类上微调模型 | 展示如何预处理数据并在关键词检测上微调预训练的语音模型 |
生物序列
笔记本 | 描述 | ||
---|---|---|---|
如何微调预训练蛋白质模型 | 查看如何对蛋白质进行标记化和微调大型预训练蛋白质“语言”模型 | ||
如何生成蛋白质折叠 | 查看如何从蛋白质序列到完整蛋白质模型和 PDB 文件 | ||
如何微调核苷酸 Transformer 模型 | 查看如何对 DNA 进行标记化和微调大型预训练 DNA“语言”模型 | ||
使用 LoRA 微调核苷酸 Transformer 模型 | 以内存高效的方式训练更大的 DNA 模型 |
其他形式
笔记本 | 描述 | ||
---|---|---|---|
概率时间序列预测 | 查看如何在自定义数据集上训练时间序列 Transformer |
实用笔记本
笔记本 | 描述 | ||
---|---|---|---|
如何将模型导出到 ONNX | 突出显示如何通过 ONNX 导出和运行推理工作负载 | ||
如何使用基准测试 | 如何使用 transformers 基准测试模型 |
TensorFlow 示例
自然语言处理
笔记本 | 描述 | ||
---|---|---|---|
训练您的分词器 | 如何训练和使用您自己的分词器 | ||
训练您的语言模型 | 如何轻松开始使用 transformers | ||
如何在文本分类上微调模型 | 展示如何预处理数据并在任何 GLUE 任务上微调预训练模型。 | ||
如何在语言建模上微调模型 | 展示如何预处理数据并在因果或掩码 LM 任务上微调预训练模型。 | ||
如何在标记分类上微调模型 | 展示如何预处理数据并在标记分类任务(NER,PoS)上微调预训练模型。 | ||
如何在问答上微调模型 | 展示如何预处理数据并在 SQUAD 上微调预训练模型。 | ||
如何在多项选择上微调模型 | 展示如何预处理数据并在 SWAG 上微调预训练模型 | ||
如何在翻译上微调模型 | 展示如何预处理数据并在 WMT 上微调预训练模型 | ||
如何在摘要上微调模型 | 展示如何预处理数据并在 XSUM 上微调预训练模型 |
计算机视觉
笔记本 | 描述 | ||
---|---|---|---|
如何在图像分类上微调模型 | 展示如何预处理数据并在图像分类上微调任何预训练的视觉模型 | ||
如何在语义分割上微调 SegFormer 模型 | 展示如何预处理数据并在语义分割上微调预训练的 SegFormer 模型 |
生物序列
笔记本 | 描述 | ||
---|---|---|---|
如何微调预训练的蛋白质模型 | 查看如何对蛋白质进行标记化,并微调一个大型预训练的蛋白质“语言”模型 |
实用笔记本
笔记本 | 描述 | ||
---|---|---|---|
如何在 TPU 上训练 TF/Keras 模型 | 查看如何在 Google 的 TPU 硬件上以高速训练 |
Optimum 笔记本
🤗 Optimum 是 🤗 Transformers 的扩展,提供一组性能优化工具,实现在目标硬件上训练和运行模型的最大效率。
笔记本 | 描述 | ||
---|---|---|---|
如何使用 ONNX Runtime 对文本分类模型进行量化 | 展示如何在任何 GLUE 任务中使用ONNX Runtime对模型应用静态和动态量化。 | ||
如何使用 Intel Neural Compressor 对文本分类模型进行量化 | 展示如何在任何 GLUE 任务中使用Intel Neural Compressor (INC)对模型应用静态、动态和感知训练量化。 | ||
如何使用 ONNX Runtime 在文本分类模型上进行微调 | 展示如何预处理数据并使用ONNX Runtime在任何 GLUE 任务上微调模型。 | ||
如何使用 ONNX Runtime 在摘要上微调模型 | 展示如何预处理数据并使用ONNX Runtime在 XSUM 上微调模型。 |
社区笔记本:
社区开发的更多笔记本可在此处找到。
社区
此页面汇集了由社区开发的🤗 Transformers 周围的资源。
社区资源:
资源 | 描述 | 作者 |
---|---|---|
Hugging Face Transformers 词汇表闪卡 | 基于 Transformers Docs 词汇表的一套闪卡,已经制作成易于使用Anki学习/复习的形式,Anki 是一款专门设计用于长期知识保留的开源、跨平台应用程序。查看这个介绍性视频,了解如何使用闪卡。 | Darigov Research |
社区笔记本:
笔记本 | 描述 | 作者 | |
---|---|---|---|
微调预训练的 Transformer 以生成歌词 | 如何通过微调 GPT-2 模型生成您最喜爱艺术家风格的歌词 | Aleksey Korshuk | |
在 Tensorflow 2 中训练 T5 | 如何使用 Tensorflow 2 为任何任务训练 T5。这个笔记本演示了在 Tensorflow 2 中实现的一个问答任务,使用 SQUAD | Muhammad Harris | |
在 TPU 上训练 T5 | 如何使用 Transformers 和 Nlp 在 SQUAD 上训练 T5 | Suraj Patil | |
为分类和多项选择微调 T5 | 如何使用 PyTorch Lightning 以文本-文本格式微调 T5 以进行分类和多项选择任务 | Suraj Patil | |
在新数据集和语言上微调 DialoGPT | 如何在新数据集上微调 DialoGPT 模型,用于开放对话聊天机器人 | Nathan Cooper | |
使用 Reformer 进行长序列建模 | 如何使用 Reformer 在长度为 500,000 个标记的序列上进行训练 | Patrick von Platen | |
为摘要微调 BART | 如何使用 blurr 使用 fastai 微调 BART 进行摘要 | Wayde Gilliam | |
为任何人的推文微调预训练 Transformer | 如何通过微调 GPT-2 模型生成您喜爱的 Twitter 账户风格的推文 | Boris Dayma | |
使用 Weights&Biases 优化🤗Hugging Face 模型 | 一个完整的教程,展示了 W&B 与 Hugging Face 的集成 | Boris Dayma | |
预训练 Longformer | 如何构建现有预训练模型的“长”版本 | Iz Beltagy | |
为 QA 微调 Longformer | 如何为 QA 任务微调 Longformer 模型 | Suraj Patil | |
使用🤗nlp 评估模型 | 如何使用nlp 在 TriviaQA 上评估 Longformer |
Patrick von Platen | |
为情感跨度提取微调 T5 | 如何使用 PyTorch Lightning 以文本到文本格式微调 T5 进行情感跨度提取 | Lorenzo Ampil | |
为多类分类微调 DistilBert | 如何使用 PyTorch 微调 DistilBert 进行多类分类 | Abhishek Kumar Mishra | |
微调 BERT 进行多标签分类 | 如何使用 PyTorch 微调 BERT 进行多标签分类 | Abhishek Kumar Mishra | |
微调 T5 进行摘要 | 如何在 PyTorch 中微调 T5 进行摘要,并使用 WandB 跟踪实验 | Abhishek Kumar Mishra | |
使用动态填充/分桶加速 Transformer 中的微调 | 如何通过动态填充/分桶将微调加速 2 倍 | Michael Benesty | |
为遮蔽语言建模预训练 Reformer | 如何训练具有双向自注意力层的 Reformer 模型 | Patrick von Platen | |
扩展和微调 Sci-BERT | 如何在 CORD 数据集上增加预训练的 SciBERT 模型的词汇量并进行流水线处理。 | Tanmay Thakur | |
使用 Trainer API 微调 BlenderBotSmall 进行摘要 | 如何在自定义数据集上使用 Trainer API 微调 BlenderBotSmall 进行摘要 | Tanmay Thakur | |
微调 Electra 并使用 Integrated Gradients 进行解释 | 如何微调 Electra 进行情感分析,并使用 Captum Integrated Gradients 解释预测 | Eliza Szczechla | |
使用 Trainer 类微调非英语 GPT-2 模型 | 如何使用 Trainer 类微调非英语 GPT-2 模型 | Philipp Schmid | |
为多标签分类任务微调 DistilBERT 模型 | 如何为多标签分类任务微调 DistilBERT 模型 | Dhaval Taunk | |
为句对分类微调 ALBERT | 如何为句对分类任务微调 ALBERT 模型或其他基于 BERT 的模型 | Nadir El Manouzi | |
Fine-tune Roberta for sentiment analysis | 如何为情感分析微调一个 Roberta 模型 | Dhaval Taunk | |
评估问题生成模型 | 您的 seq2seq 变压器模型生成的问题的答案有多准确? | Pascal Zoleko | |
使用 DistilBERT 和 Tensorflow 对文本进行分类 | 如何在 TensorFlow 中为文本分类微调 DistilBERT | Peter Bayerle | |
利用 BERT 进行 CNN/Dailymail 的编码器-解码器摘要 | 如何使用bert-base-uncased检查点对 CNN/Dailymail 的摘要进行热启动EncoderDecoderModel | Patrick von Platen | |
利用 RoBERTa 进行 BBC XSum 的编码器-解码器摘要 | 如何使用roberta-base检查点对 BBC/XSum 的摘要进行热启动共享EncoderDecoderModel | Patrick von Platen | |
在顺序问答(SQA)上微调 TAPAS | 如何在顺序问答(SQA)数据集上使用tapas-base检查点微调TapasForQuestionAnswering | Niels Rogge | |
在 Table Fact Checking(TabFact)上评估 TAPAS | 如何使用🤗数据集和🤗transformers 库的组合评估经过微调的TapasForSequenceClassification,使用tapas-base-finetuned-tabfact检查点 | Niels Rogge | |
为翻译微调 mBART | 如何使用 Seq2SeqTrainer 为印地语到英语翻译微调 mBART | Vasudev Gupta | |
在 FUNSD 上微调 LayoutLM(一种表单理解数据集) | 如何在 FUNSD 数据集上微调LayoutLMForTokenClassification,从扫描文档中提取信息 | Niels Rogge | |
微调 DistilGPT2 并生成文本 | 如何微调 DistilGPT2 并生成文本 | Aakash Tripathi | |
在最多 8K 标记上微调 LED | 如何在 pubmed 上微调 LED 进行长距离摘要 | Patrick von Platen | |
在 Arxiv 上评估 LED | 如何有效评估 LED 进行长距离摘要 | Patrick von Platen | |
在 RVL-CDIP 上微调 LayoutLM(一种文档图像分类数据集) | 如何在 RVL-CDIP 数据集上微调LayoutLMForSequenceClassification,用于扫描文档分类 | Niels Rogge | |
- 使用 GPT2 调整进行 Wav2Vec2 CTC 解码 | 如何使用语言模型调整解码 CTC 序列 | Eric Lam | |
- 使用 Trainer 类在两种语言中对 BART 进行摘要微调 | 如何使用 Trainer 类在两种语言中对 BART 进行摘要微调 | Eliza Szczechla | |
- 在 Trivia QA 上评估 Big Bird | 如何在 Trivia QA 上评估 BigBird 在长文档问答上的表现 | Patrick von Platen | |
- 使用 Wav2Vec2 创建视频字幕 | 如何通过使用 Wav2Vec 转录音频来从任何视频创建 YouTube 字幕 | Niklas Muennighoff | |
- 使用 PyTorch Lightning 在 CIFAR-10 上微调 Vision Transformer | 如何使用 HuggingFace Transformers、Datasets 和 PyTorch Lightning 在 CIFAR-10 上微调 Vision Transformer(ViT) | Niels Rogge | |
- 使用🤗 Trainer 在 CIFAR-10 上微调 Vision Transformer | 如何使用 HuggingFace Transformers、Datasets 和🤗 Trainer 在 CIFAR-10 上微调 Vision Transformer(ViT) | Niels Rogge | |
- 在 Open Entity 上评估 LUKE,一个实体类型数据集 | 如何在 Open Entity 数据集上评估LukeForEntityClassification | Ikuya Yamada | |
- 在 TACRED 上评估 LUKE,一个关系抽取数据集 | 如何在 TACRED 数据集上评估LukeForEntityPairClassification | Ikuya Yamada | |
在 CoNLL-2003 上评估 LUKE,一个重要的 NER 基准 | 如何在 CoNLL-2003 数据集上评估LukeForEntitySpanClassification | Ikuya Yamada | |
在 PubMed 数据集上评估 BigBird-Pegasus | 如何在 PubMed 数据集上评估BigBirdPegasusForConditionalGeneration | Vasudev Gupta | |
使用 Wav2Vec2 进行语音情感分类 | 如何利用预训练的 Wav2Vec2 模型对 MEGA 数据集进行情感分类 | Mehrdad Farahani | |
使用 DETR 在图像中检测对象 | 如何使用训练好的DetrForObjectDetection模型在图像中检测对象并可视化注意力 | Niels Rogge | |
在自定义对象检测数据集上微调 DETR | 如何在自定义对象检测数据集上微调DetrForObjectDetection | Niels Rogge | |
为命名实体识别微调 T5 | 如何在命名实体识别任务上微调T5 | Ogundepo Odunayo |
自定义工具和提示
如果您不了解 transformers 上下文中的工具和代理是什么,我们建议您先阅读 Transformers Agents 页面。
Transformers Agents 是一个实验性 API,随时可能发生变化。代理返回的结果可能会有所不同,因为 API 或底层模型可能会发生变化。
创建和使用自定义工具和提示对于赋予代理能力并使其执行新任务至关重要。在本指南中,我们将看一下:
-
如何自定义提示
-
如何使用自定义工具
-
如何创建自定义工具
自定义提示
如 Transformers Agents 中所解释的,代理可以在 run()和 chat()模式下运行。run
和 chat
模式都基于相同的逻辑。驱动代理的语言模型是基于一个长提示进行条件化,并通过生成下一个标记来完成提示,直到达到停止标记。两种模式之间唯一的区别是,在 chat
模式期间,提示会扩展为先前用户输入和模型生成。这使得代理可以访问过去的交互,似乎给代理一定的记忆。
提示的结构
让我们更仔细地看一下提示的结构,以了解如何最好地定制它。提示大体上分为四个部分。
-
- 介绍:代理应该如何行为,工具概念的解释。
-
- 所有工具的描述。这由一个
<<all_tools>>
标记定义,它在运行时动态替换为用户定义/选择的工具。
- 所有工具的描述。这由一个
-
- 一组任务及其解决方案的示例
-
- 当前示例和解决方案请求。
为了更好地理解每个部分,让我们看一下 run
提示的缩短版本可能是什么样子:
I will ask you to perform a task, your job is to come up with a series of simple commands in Python that will perform the task.
[...]
You can print intermediate results if it makes sense to do so.
Tools:
- document_qa: This is a tool that answers a question about a document (pdf). It takes an input named `document` which should be the document containing the information, as well as a `question` that is the question about the document. It returns a text that contains the answer to the question.
- image_captioner: This is a tool that generates a description of an image. It takes an input named `image` which should be the image to the caption and returns a text that contains the description in English.
[...]
Task: "Answer the question in the variable `question` about the image stored in the variable `image`. The question is in French."
I will use the following tools: `translator` to translate the question into English and then `image_qa` to answer the question on the input image.
Answer:
```py
translated_question = translator(question=question, src_lang="French", tgt_lang="English")
print(f"翻译后的问题是 {translated_question}。")
answer = image_qa(image=image, question=translated_question)
print(f"答案是 {answer}")
```py
Task: "Identify the oldest person in the `document` and create an image showcasing the result as a banner."
I will use the following tools: `document_qa` to find the oldest person in the document, then `image_generator` to generate an image according to the answer.
Answer:
```py
answer = document_qa(document, question="最年长的人是谁?")
print(f"答案是 {answer}。")
image = image_generator("显示 " + answer + " 的横幅")
```py
[...]
Task: "Draw me a picture of rivers and lakes"
I will use the following
介绍(“工具:”之前的文本)精确解释了模型应该如何行为以及它应该做什么。这部分很可能不需要定制,因为代理应该始终以相同的方式行为。
第二部分(“工具”下面的项目符号)在调用 run
或 chat
时动态添加。agent.toolbox
中有多少工具,就有多少项目符号,每个项目符号包括工具的名称和描述:
- <tool.name>: <tool.description>
让我们通过加载 document_qa 工具并打印出名称和描述来快速验证这一点。
from transformers import load_tool
document_qa = load_tool("document-question-answering")
print(f"- {document_qa.name}: {document_qa.description}")
这给出了:
- document_qa: This is a tool that answers a question about a document (pdf). It takes an input named `document` which should be the document containing the information, as well as a `question` that is the question about the document. It returns a text that contains the answer to the question.
我们可以看到工具名称简短而准确。描述包括两部分,第一部分解释工具的功能,第二部分说明预期的输入参数和返回值。
一个好的工具名称和工具描述对于代理正确使用它非常重要。请注意,代理对于工具的唯一信息是其名称和描述,因此应确保两者都写得准确,并与工具箱中现有工具的风格匹配。特别要确保描述中提到所有预期的参数名称,以代码样式列出,以及预期的类型和它们的描述。
检查精心策划的 Transformers 工具的命名和描述,以更好地了解工具应该具有的名称和描述。您可以通过 Agent.toolbox
属性查看所有工具。
第三部分包括一组精心策划的示例,向代理展示它应该为何种用户请求生成什么样的代码。赋予代理力量的大型语言模型非常擅长识别提示中的模式,并使用新数据重复该模式。因此,非常重要的是示例以最大化代理生成正确可执行代码的可能性的方式编写。
让我们看一个例子:
Task: "Identify the oldest person in the `document` and create an image showcasing the result as a banner."
I will use the following tools: `document_qa` to find the oldest person in the document, then `image_generator` to generate an image according to the answer.
Answer:
```py
answer = document_qa(document, question="What is the oldest person?")
print(f"答案是 {answer}。")
image = image_generator("显示" + answer + "的横幅")
```py
模型被提示重复的模式有三部分:任务陈述,代理解释其打算做什么,最后是生成的代码。提示中的每个示例都具有这个确切的模式,从而确保代理在生成新标记时会重现完全相同的模式。
提示示例由 Transformers 团队精心策划,并在一组问题陈述上进行严格评估,以确保代理的提示尽可能好地解决代理的真实用例。
提示的最后部分对应于:
Task: "Draw me a picture of rivers and lakes"
I will use the following
是代理被要求完成的最终未完成示例。未完成的示例是根据实际用户输入动态创建的。对于上面的示例,用户运行了:
agent.run("Draw me a picture of rivers and lakes")
用户输入 - 即任务:“给我画一幅河流和湖泊的图片”被转换为提示模板:“任务:
不要深入细节,聊天模板具有相同的提示结构,示例具有稍微不同的风格,例如:
[...]
=====
Human: Answer the question in the variable `question` about the image stored in the variable `image`.
Assistant: I will use the tool `image_qa` to answer the question on the input image.
```py
answer = image_qa(text=question, image=image)
打印(f"答案是 {answer}")
```py
Human: I tried this code, it worked but didn't give me a good result. The question is in French
Assistant: In this case, the question needs to be translated first. I will use the tool `translator` to do this.
```py
translated_question = translator(question=question, src_lang="French", tgt_lang="English")
打印(f"翻译后的问题是 {translated_question}。")
answer = image_qa(text=translated_question, image=image)
打印(f"答案是 {answer}")
```py
=====
[...]
与run
提示的示例相反,每个chat
提示示例都有一个或多个人类和助手之间的交流。每个交流的结构与run
提示的示例类似。用户的输入附加在人类:后面,代理被提示首先生成需要完成的内容,然后再生成代码。一个交流可以基于先前的交流,因此允许用户参考先前的交流,就像用户上面的输入“我尝试这段代码”引用了代理先前生成的代码一样。
运行.chat
后,用户的输入或任务被转换为一个未完成的示例,形式如下:
Human: <user-input>\n\nAssistant:
代理完成。与run
命令相反,chat
命令会将已完成的示例附加到提示中,从而为下一个chat
轮提供更多上下文。
现在我们知道了提示的结构,让我们看看如何自定义它!
编写良好的用户输入
虽然大型语言模型在理解用户意图方面变得越来越好,但尽可能准确地帮助代理选择正确的任务会极大地有所帮助。什么是尽可能准确?
代理在其提示中看到一系列工具名称及其描述。添加的工具越多,代理选择正确工具的难度就越大,选择正确的工具运行的顺序也变得更加困难。让我们看一个常见的失败案例,这里我们只返回分析代码。
from transformers import HfAgent
agent = HfAgent("https://api-inference.huggingface.co/models/bigcode/starcoder")
agent.run("Show me a tree", return_code=True)
给出:
==Explanation from the agent==
I will use the following tool: `image_segmenter` to create a segmentation mask for the image.
==Code generated by the agent==
mask = image_segmenter(image, prompt="tree")
这可能不是我们想要的。相反,更有可能的是我们希望生成一棵树的图像。为了更多地引导代理程序使用特定工具,因此使用工具名称和描述中存在的重要关键词可能非常有帮助。让我们看一看。
agent.toolbox["image_generator"].description
'This is a tool that creates an image according to a prompt, which is a text description. It takes an input named `prompt` which contains the image description and outputs an image.
名称和描述使用了关键词“图像”、“提示”、“创建”和“生成”。在这里使用这些词可能会更有效。让我们稍微完善一下我们的提示。
agent.run("Create an image of a tree", return_code=True)
给出:
==Explanation from the agent==
I will use the following tool `image_generator` to generate an image of a tree.
==Code generated by the agent==
image = image_generator(prompt="tree")
好多了!这看起来更像我们想要的。简而言之,当您注意到代理程序在正确将您的任务映射到正确的工具时遇到困难时,请尝试查找工具名称和描述的最相关关键词,并尝试用它来完善您的任务请求。
自定义工具描述
正如我们之前所看到的,代理程序可以访问每个工具的名称和描述。基本工具应该具有非常精确的名称和描述,但是,您可能会发现,为了您的特定用例,更改工具的描述或名称可能会有所帮助。当您添加了多个非常相似的工具或者只想将代理程序用于特定领域时,这可能变得尤为重要,例如图像生成和转换。
一个常见问题是,当代理程序在图像生成任务中经常混淆图像生成和图像转换/修改时,例如
agent.run("Make an image of a house and a car", return_code=True)
返回
==Explanation from the agent==
I will use the following tools `image_generator` to generate an image of a house and `image_transformer` to transform the image of a car into the image of a house.
==Code generated by the agent==
house_image = image_generator(prompt="A house")
car_image = image_generator(prompt="A car")
house_car_image = image_transformer(image=car_image, prompt="A house")
这可能不是我们想要的。看起来代理程序很难理解image_generator
和image_transformer
之间的区别,并经常同时使用这两者。
我们可以通过更改image_transformer
的工具名称和描述来帮助代理程序。我们可以将其称为modifier
,以使其与“图像”和“提示”有些脱离关系:
agent.toolbox["modifier"] = agent.toolbox.pop("image_transformer")
agent.toolbox["modifier"].description = agent.toolbox["modifier"].description.replace(
"transforms an image according to a prompt", "modifies an image"
)
现在,“修改”是一个强烈的提示,可以使用新的图像处理器来帮助完成上述提示。让我们再次运行它。
agent.run("Make an image of a house and a car", return_code=True)
现在我们得到了:
==Explanation from the agent==
I will use the following tools: `image_generator` to generate an image of a house, then `image_generator` to generate an image of a car.
==Code generated by the agent==
house_image = image_generator(prompt="A house")
car_image = image_generator(prompt="A car")
这绝对更接近我们的想法!但是,我们希望在同一张图片中同时有房子和汽车。将任务更多地转向单张图片生成应该会有所帮助:
agent.run("Create image: 'A house and car'", return_code=True)
==Explanation from the agent==
I will use the following tool: `image_generator` to generate an image.
==Code generated by the agent==
image = image_generator(prompt="A house and car")
对于许多用例,代理程序仍然很脆弱,特别是在生成多个对象的图像等稍微复杂的用例中。代理程序本身和基础提示将在未来几个月进一步改进,以确保代理程序对各种用户输入更加稳健。
自定义整个提示
为了给用户最大的灵活性,整个提示模板如上所述可以被用户覆盖。在这种情况下,请确保您的自定义提示包括一个介绍部分、一个工具部分、一个示例部分和一个未完成示例部分。如果您想覆盖run
提示模板,可以按照以下步骤操作:
template = """ [...] """
agent = HfAgent(your_endpoint, run_prompt_template=template)
请确保在template
中的某处定义<<all_tools>>
字符串和<<prompt>>
,以便代理程序可以了解可用的工具,并正确插入用户的提示。
同样,用户可以覆盖chat
提示模板。请注意,chat
模式始终使用以下格式进行交流:
Human: <<task>>
Assistant:
因此,重要的是自定义chat
提示模板的示例也要使用这种格式。您可以在实例化时覆盖chat
模板,如下所示。
template = """ [...] """
agent = HfAgent(url_endpoint=your_endpoint, chat_prompt_template=template)
请确保在template
中的某处定义<<all_tools>>
字符串,以便代理程序可以了解可用的工具。
在这两种情况下,如果您想使用社区中某人托管的模板,可以传递一个存储库 ID 而不是提示模板。默认提示位于此存储库中作为示例。
要在 Hub 上上传您的自定义提示并与社区共享,请确保:
-
使用数据集存储库
-
将
run
命令的提示模板放在名为run_prompt_template.txt
的文件中 -
将
chat
命令的提示模板放在名为chat_prompt_template.txt
的文件中
使用自定义工具
在本节中,我们将利用两个现有的特定于图像生成的自定义工具:
-
我们用diffusers/controlnet-canny-tool替换huggingface-tools/image-transformation,以允许进行更多图像修改。
-
我们向默认工具箱添加一个新的图像升频工具:diffusers/latent-upscaler-tool替换现有的图像转换工具。
我们将通过方便的 load_tool()函数加载自定义工具:
from transformers import load_tool
controlnet_transformer = load_tool("diffusers/controlnet-canny-tool")
upscaler = load_tool("diffusers/latent-upscaler-tool")
在向代理添加自定义工具时,工具的描述和名称会自动包含在代理的提示中。因此,自定义工具必须有一个写得很好的描述和名称,以便代理了解如何使用它们。让我们看一下controlnet_transformer
的描述和名称:
print(f"Description: '{controlnet_transformer.description}'")
print(f"Name: '{controlnet_transformer.name}'")
给出
Description: 'This is a tool that transforms an image with ControlNet according to a prompt.
It takes two inputs: `image`, which should be the image to transform, and `prompt`, which should be the prompt to use to change it. It returns the modified image.'
Name: 'image_transformer'
名称和描述准确,并符合精心策划的工具集的风格。接下来,让我们用controlnet_transformer
和upscaler
实例化一个代理:
tools = [controlnet_transformer, upscaler]
agent = HfAgent("https://api-inference.huggingface.co/models/bigcode/starcoder", additional_tools=tools)
此命令应该给您以下信息:
image_transformer has been replaced by <transformers_modules.diffusers.controlnet-canny-tool.bd76182c7777eba9612fc03c0
8718a60c0aa6312.image_transformation.ControlNetTransformationTool object at 0x7f1d3bfa3a00> as provided in `additional_tools`
精心策划的工具集已经有一个image_transformer
工具,现在用我们的自定义工具替换。
覆盖现有工具可以是有益的,如果我们想要为与现有工具完全相同的任务使用自定义工具,因为代理擅长使用特定任务。请注意,自定义工具在这种情况下应遵循与被覆盖工具完全相同的 API,或者您应该调整提示模板以确保使用该工具的所有示例都已更新。
升频工具被命名为image_upscaler
,该工具尚未出现在默认工具箱中,因此只需将其添加到工具列表中。您可以随时查看代理当前可用的工具箱,通过agent.toolbox
属性:
print("\n".join([f"- {a}" for a in agent.toolbox.keys()]))
- document_qa
- image_captioner
- image_qa
- image_segmenter
- transcriber
- summarizer
- text_classifier
- text_qa
- text_reader
- translator
- image_transformer
- text_downloader
- image_generator
- video_generator
- image_upscaler
请注意,image_upscaler
现在是代理工具箱的一部分。
现在让我们尝试一下新工具!我们将重复使用我们在 Transformers Agents Quickstart 中生成的图像。
from diffusers.utils import load_image
image = load_image(
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/rivers_and_lakes.png"
)
让我们将图像转换为美丽的冬季风景:
image = agent.run("Transform the image: 'A frozen lake and snowy forest'", image=image)
==Explanation from the agent==
I will use the following tool: `image_transformer` to transform the image.
==Code generated by the agent==
image = image_transformer(image, prompt="A frozen lake and snowy forest")
新的图像处理工具基于 ControlNet,可以对图像进行非常强烈的修改。默认情况下,图像处理工具返回大小为 512x512 像素的图像。让我们看看是否可以将其升频。
image = agent.run("Upscale the image", image)
==Explanation from the agent==
I will use the following tool: `image_upscaler` to upscale the image.
==Code generated by the agent==
upscaled_image = image_upscaler(image)
代理根据升频工具的描述和名称自动将我们的提示“将图像升频”映射到刚刚添加的升频工具,并能够正确运行它。
接下来,让我们看看如何创建一个新的自定义工具。
添加新工具
在本节中,我们将展示如何创建一个可以添加到代理的新工具。
创建一个新工具
我们首先将通过创建一个工具来开始。我们将添加一个不太有用但有趣的任务,即获取 Hugging Face Hub 上针对给定任务下载量最高的模型。
我们可以使用以下代码来实现:
from huggingface_hub import list_models
task = "text-classification"
model = next(iter(list_models(filter=task, sort="downloads", direction=-1)))
print(model.id)
对于任务text-classification
,返回'facebook/bart-large-mnli'
,对于translation
,返回`'t5-base'。
我们如何将其转换为代理可以利用的工具?所有工具都依赖于保存主要属性的超类Tool
。我们将创建一个继承自它的类:
from transformers import Tool
class HFModelDownloadsTool(Tool):
pass
这个类有一些需求:
-
一个
name
属性,对应于工具本身的名称。为了与具有表现性名称的其他工具保持一致,我们将其命名为model_download_counter
。 -
一个
description
属性,将用于填充代理的提示。 -
inputs
和outputs
属性。定义这些属性将帮助 Python 解释器做出明智的选择,并允许在将工具推送到 Hub 时生成一个 gradio-demo。它们都是预期值的列表,可以是text
、image
或audio
。 -
包含推理代码的
__call__
方法。这就是我们上面玩耍的代码!
现在我们的类是这样的:
from transformers import Tool
from huggingface_hub import list_models
class HFModelDownloadsTool(Tool):
name = "model_download_counter"
description = (
"This is a tool that returns the most downloaded model of a given task on the Hugging Face Hub. "
"It takes the name of the category (such as text-classification, depth-estimation, etc), and "
"returns the name of the checkpoint."
)
inputs = ["text"]
outputs = ["text"]
def __call__(self, task: str):
model = next(iter(list_models(filter=task, sort="downloads", direction=-1)))
return model.id
现在我们有了我们的工具。将其保存在一个文件中,并从您的主脚本中导入它。让我们将这个文件命名为 model_downloads.py
,这样生成的导入代码看起来像这样:
from model_downloads import HFModelDownloadsTool
tool = HFModelDownloadsTool()
为了让其他人从中受益,并为了更简单的初始化,我们建议将其推送到 Hub 在您的命名空间下。要这样做,只需在 tool
变量上调用 push_to_hub
:
tool.push_to_hub("hf-model-downloads")
现在您的代码已经在 Hub 上了!让我们看看最后一步,即让代理使用它。
让代理使用工具
现在我们有了我们的工具,它存放在 Hub 上,可以这样实例化(将用户名更改为您的工具):
from transformers import load_tool
tool = load_tool("lysandre/hf-model-downloads")
为了在代理中使用它,只需将其传递给代理初始化方法的 additional_tools
参数:
from transformers import HfAgent
agent = HfAgent("https://api-inference.huggingface.co/models/bigcode/starcoder", additional_tools=[tool])
agent.run(
"Can you read out loud the name of the model that has the most downloads in the 'text-to-video' task on the Hugging Face Hub?"
)
输出如下:
==Code generated by the agent==
model = model_download_counter(task="text-to-video")
print(f"The model with the most downloads is {model}.")
audio_model = text_reader(model)
==Result==
The model with the most downloads is damo-vilab/text-to-video-ms-1.7b.
并生成以下音频。
音频 |
---|
|
https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/damo.wav
|
根据 LLM 的不同,有些可能非常脆弱,需要非常精确的提示才能很好地工作。拥有工具的明确定义的名称和描述对于代理能够利用它至关重要。
替换现有工具
通过将新项目分配给代理的工具箱,可以简单地替换现有工具。以下是如何操作的方法:
from transformers import HfAgent, load_tool
agent = HfAgent("https://api-inference.huggingface.co/models/bigcode/starcoder")
agent.toolbox["image-transformation"] = load_tool("diffusers/controlnet-canny-tool")
替换工具时要小心!这也会调整代理的提示。如果您有一个更适合任务的更好提示,这可能是好事,但也可能导致您的工具被选中的次数远远超过其他工具,或者选择其他工具而不是您定义的工具。
利用 gradio-tools
gradio-tools 是一个强大的库,允许使用 Hugging Face Spaces 作为工具。它支持许多现有的 Spaces,以及可以使用它设计自定义 Spaces。
我们通过使用 Tool.from_gradio
方法为 gradio_tools
提供支持。例如,我们想利用 gradio-tools
工具包中提供的 StableDiffusionPromptGeneratorTool
工具来改进我们的提示并生成更好的图像。
我们首先从 gradio_tools
导入工具并实例化它:
from gradio_tools import StableDiffusionPromptGeneratorTool
gradio_tool = StableDiffusionPromptGeneratorTool()
我们将该实例传递给 Tool.from_gradio
方法:
from transformers import Tool
tool = Tool.from_gradio(gradio_tool)
现在我们可以像处理通常的自定义工具一样管理它。我们利用它来改进我们的提示 一只穿着太空服的兔子
:
from transformers import HfAgent
agent = HfAgent("https://api-inference.huggingface.co/models/bigcode/starcoder", additional_tools=[tool])
agent.run("Generate an image of the `prompt` after improving it.", prompt="A rabbit wearing a space suit")
模型充分利用了该工具:
==Explanation from the agent==
I will use the following tools: `StableDiffusionPromptGenerator` to improve the prompt, then `image_generator` to generate an image according to the improved prompt.
==Code generated by the agent==
improved_prompt = StableDiffusionPromptGenerator(prompt)
print(f"The improved prompt is {improved_prompt}.")
image = image_generator(improved_prompt)
最后生成图像之前:
gradio-tools 需要文本输入和输出,即使在处理不同的模态时也是如此。这个实现可以处理图像和音频对象。目前这两者是不兼容的,但随着我们努力改进支持,它们将迅速变得兼容。
未来与 Langchain 的兼容性
我们喜欢 Langchain,并认为它拥有一个非常引人注目的工具套件。为了处理这些工具,Langchain 需要文本输入和输出,即使在处理不同的模态时也是如此。这通常是对象的序列化版本(即保存到磁盘)。
这种差异意味着 transformers-agents 和 langchain 之间不能处理多模态。我们希望在未来版本中解决这个限制,并欢迎热衷于 langchain 的用户帮助我们实现这种兼容性。
我们希望能得到更好的支持。如果您愿意帮助,请提出问题,并分享您的想法。
故障排除
原文链接:
huggingface.co/docs/transformers/v4.37.2/en/troubleshooting
有时会出现错误,但我们在这里帮助您!本指南涵盖了我们见过的一些最常见问题以及您可以如何解决它们。但是,本指南并不旨在成为每个🤗 Transformers 问题的全面集合。如需更多有关故障排除的帮助,请尝试:
www.youtube-nocookie.com/embed/S2EEG3JIt2A
- 在论坛上寻求帮助。您可以将问题发布到特定类别,如初学者或🤗 Transformers。请确保编写一个具有一些可重现代码的良好描述性论坛帖子,以最大程度地提高解决问题的可能性!
www.youtube-nocookie.com/embed/_PAli-V4wj0
-
如果是与库相关的错误,请在🤗 Transformers 存储库上创建一个Issue。尽量包含尽可能多描述错误的信息,以帮助我们更好地找出问题所在以及如何修复它。
-
如果您使用较旧版本的🤗 Transformers,请查看迁移指南,因为在版本之间引入了一些重要更改。
有关故障排除和获取帮助的更多详细信息,请查看 Hugging Face 课程的第八章。
防火墙环境
一些云端和内部设置的 GPU 实例被防火墙阻止对外部连接,导致连接错误。当您的脚本尝试下载模型权重或数据集时,下载将挂起,然后超时并显示以下消息:
ValueError: Connection error, and we cannot find the requested files in the cached path.
Please try again or make sure your Internet connection is on.
在这种情况下,您应该尝试在离线模式下运行🤗 Transformers 以避免连接错误。
CUDA 内存不足
在没有适当硬件的情况下训练拥有数百万参数的大型模型可能会很具挑战性。当 GPU 内存不足时,您可能会遇到的常见错误是:
CUDA out of memory. Tried to allocate 256.00 MiB (GPU 0; 11.17 GiB total capacity; 9.70 GiB already allocated; 179.81 MiB free; 9.85 GiB reserved in total by PyTorch)
以下是一些潜在的解决方案,您可以尝试减少内存使用:
-
减少 TrainingArguments 中的
per_device_train_batch_size
值。 -
尝试在 TrainingArguments 中使用
gradient_accumulation_steps
来有效增加总体批量大小。
有关节省内存技术的更多详细信息,请参考性能指南。
无法加载保存的 TensorFlow 模型
TensorFlow 的model.save方法将整个模型(架构、权重、训练配置)保存在单个文件中。然而,当您再次加载模型文件时,可能会遇到错误,因为🤗 Transformers 可能不会加载模型文件中的所有与 TensorFlow 相关的对象。为避免保存和加载 TensorFlow 模型时出现问题,我们建议您:
- 使用
model.save_weights
将模型权重保存为h5
文件扩展名,然后使用 from_pretrained()重新加载模型:
>>> from transformers import TFPreTrainedModel
>>> from tensorflow import keras
>>> model.save_weights("some_folder/tf_model.h5")
>>> model = TFPreTrainedModel.from_pretrained("some_folder")
- 使用
~TFPretrainedModel.save_pretrained
保存模型,然后使用 from_pretrained()再次加载它:
>>> from transformers import TFPreTrainedModel
>>> model.save_pretrained("path_to/model")
>>> model = TFPreTrainedModel.from_pretrained("path_to/model")
ImportError
您可能会遇到另一种常见错误,特别是对于新发布的模型,即ImportError
:
ImportError: cannot import name 'ImageGPTImageProcessor' from 'transformers' (unknown location)
对于这些错误类型,请确保您已安装了最新版本的🤗 Transformers 以访问最新的模型:
pip install transformers --upgrade
CUDA error: device-side assert triggered
有时您可能会遇到有关设备代码错误的通用 CUDA 错误。
RuntimeError: CUDA error: device-side assert triggered
您应该首先尝试在 CPU 上运行代码,以获得更详细的错误消息。将以下环境变量添加到您的代码开头以切换到 CPU:
>>> import os
>>> os.environ["CUDA_VISIBLE_DEVICES"] = ""
另一个选项是从 GPU 获取更好的回溯。将以下环境变量添加到您的代码开头,以使回溯指向错误源:
>>> import os
>>> os.environ["CUDA_LAUNCH_BLOCKING"] = "1"
当填充标记未被屏蔽时输出不正确
在某些情况下,如果input_ids
包含填充标记,则输出的hidden_state
可能是不正确的。为了演示,加载一个模型和分词器。您可以访问模型的pad_token_id
以查看其值。对于一些模型,pad_token_id
可能为None
,但您总是可以手动设置它。
>>> from transformers import AutoModelForSequenceClassification
>>> import torch
>>> model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased")
>>> model.config.pad_token_id
0
以下示例显示了不屏蔽填充标记时的输出:
>>> input_ids = torch.tensor([[7592, 2057, 2097, 2393, 9611, 2115], [7592, 0, 0, 0, 0, 0]])
>>> output = model(input_ids)
>>> print(output.logits)
tensor([[ 0.0082, -0.2307],
[ 0.1317, -0.1683]], grad_fn=<AddmmBackward0>)
以下是第二个序列的实际输出:
>>> input_ids = torch.tensor([[7592]])
>>> output = model(input_ids)
>>> print(output.logits)
tensor([[-0.1008, -0.4061]], grad_fn=<AddmmBackward0>)
大多数情况下,您应该为您的模型提供一个attention_mask
来忽略填充标记,以避免这种潜在错误。现在第二个序列的输出与其实际输出匹配:
默认情况下,分词器根据特定分词器的默认值为您创建attention_mask
。
>>> attention_mask = torch.tensor([[1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0]])
>>> output = model(input_ids, attention_mask=attention_mask)
>>> print(output.logits)
tensor([[ 0.0082, -0.2307],
[-0.1008, -0.4061]], grad_fn=<AddmmBackward0>)
🤗 Transformers 不会自动创建attention_mask
来屏蔽填充标记,如果提供了填充标记,因为:
-
一些模型没有填充标记。
-
对于某些用例,用户希望模型关注填充标记。
ValueError: Unrecognized configuration class XYZ for this kind of AutoModel
通常,我们建议使用 AutoModel 类来加载预训练模型的实例。这个类可以根据配置自动推断和加载给定检查点中的正确架构。如果在从检查点加载模型时看到ValueError
,这意味着 Auto 类无法从给定检查点中的配置找到到您尝试加载的模型类型的映射。最常见的情况是,当检查点不支持给定任务时会发生这种情况。例如,在以下示例中,您将看到此错误,因为没有用于问答的 GPT2:
>>> from transformers import AutoProcessor, AutoModelForQuestionAnswering
>>> processor = AutoProcessor.from_pretrained("gpt2-medium")
>>> model = AutoModelForQuestionAnswering.from_pretrained("gpt2-medium")
ValueError: Unrecognized configuration class <class 'transformers.models.gpt2.configuration_gpt2.GPT2Config'> for this kind of AutoModel: AutoModelForQuestionAnswering.
Model type should be one of AlbertConfig, BartConfig, BertConfig, BigBirdConfig, BigBirdPegasusConfig, BloomConfig, ...
性能和可伸缩性
性能和可伸缩性
原文链接:
huggingface.co/docs/transformers/v4.37.2/en/performance
训练大型 Transformer 模型并将其部署到生产环境中会带来各种挑战。
在训练过程中,模型可能需要比可用 GPU 内存更多的 GPU 内存,或者表现出训练速度较慢。在部署阶段,模型可能难以处理生产环境中所需的吞吐量。
本文档旨在帮助您克服这些挑战,并找到适合您用例的最佳设置。指南分为训练和推理部分,因为每个部分都有不同的挑战和解决方案。在每个部分中,您将找到针对不同硬件配置的单独指南,例如单个 GPU 与多个 GPU 进行训练,或 CPU 与 GPU 进行推理。
将本文档作为您进一步导航到与您的情况匹配的方法的起点。
训练
训练大型 Transformer 模型高效地需要像 GPU 或 TPU 这样的加速器。最常见的情况是你只有一块 GPU。您可以应用的方法来提高单个 GPU 上的训练效率也适用于其他设置,如多个 GPU。然而,也有一些特定于多 GPU 或 CPU 训练的技术。我们在单独的部分中进行介绍。
-
在单个 GPU 上进行高效训练的方法和工具:从这里开始学习可以帮助优化 GPU 内存利用率、加快训练速度或两者兼具的常见方法。
-
多 GPU 训练部分:探索本节,了解适用于多 GPU 设置的进一步优化方法,如数据、张量和管道并行。
-
CPU 训练部分:了解在 CPU 上进行混合精度训练。
-
在多个 CPU 上进行高效训练:了解分布式 CPU 训练。
-
使用 TensorFlow 在 TPU 上训练:如果您是 TPU 的新手,请参考本节,了解在 TPU 上训练和使用 XLA 的主观介绍。
-
用于训练的自定义硬件:在构建自己的深度学习装置时找到技巧和窍门。
-
使用 Trainer API 进行超参数搜索
推理
在生产环境中使用大型模型进行高效推理可能与训练它们一样具有挑战性。在接下来的部分中,我们将介绍在 CPU 和单/多 GPU 设置上运行推理的步骤。
-
在单个 CPU 上进行推理
-
在单个 GPU 上进行推理
-
多 GPU 推理
-
XLA 集成用于 TensorFlow 模型
训练和推理
在这里,您将找到适用于训练模型或使用模型进行推理的技术、提示和技巧。
-
实例化一个大模型
-
性能问题的故障排除
贡献
这份文档远未完整,还有很多需要添加的内容,所以如果您有补充或更正,请不要犹豫,打开一个 PR,或者如果您不确定,请开始一个 Issue,我们可以在那里讨论细节。
在提出 A 优于 B 的贡献时,请尽量包含可重现的基准测试和/或指向该信息来源的链接(除非信息直接来自您)。
量化
原文链接:
huggingface.co/docs/transformers/v4.37.2/en/quantization
量化技术专注于用更少的信息表示数据,同时也试图不丢失太多准确性。这通常意味着将数据类型转换为用更少的位表示相同信息。例如,如果您的模型权重存储为 32 位浮点数,并且它们被量化为 16 位浮点数,这将使模型大小减半,使其更容易存储并减少内存使用。较低的精度也可以加快推理速度,因为使用更少的位进行计算需要更少的时间。
Transformers 支持几种量化方案,帮助您在大型语言模型(LLMs)上运行推理和在量化模型上微调适配器。本指南将向您展示如何使用激活感知权重量化(AWQ)、AutoGPTQ 和 bitsandbytes。
AWQ
尝试使用这个notebook进行 AWQ 量化!
激活感知权重量化(AWQ)不会量化模型中的所有权重,而是保留对 LLM 性能重要的一小部分权重。这显著减少了量化损失,使您可以在不经历任何性能降级的情况下以 4 位精度运行模型。
有几个库可用于使用 AWQ 算法量化模型,例如llm-awq、autoawq或optimum-intel。 Transformers 支持加载使用 llm-awq 和 autoawq 库量化的模型。本指南将向您展示如何加载使用 autoawq 量化的模型,但对于使用 llm-awq 量化的模型,过程类似。
确保您已安装 autoawq:
pip install autoawq
可以通过检查模型的config.json文件中的quantization_config
属性来识别 AWQ 量化模型:
{
"_name_or_path": "/workspace/process/huggingfaceh4_zephyr-7b-alpha/source",
"architectures": [
"MistralForCausalLM"
],
...
...
...
"quantization_config": {
"quant_method": "awq",
"zero_point": true,
"group_size": 128,
"bits": 4,
"version": "gemm"
}
}
使用 from_pretrained()方法加载量化模型。如果您在 CPU 上加载了模型,请确保首先将其移动到 GPU 设备上。使用device_map
参数指定模型放置的位置:
from transformers import AutoModelForCausalLM, AutoTokenizer
model_id = "TheBloke/zephyr-7B-alpha-AWQ"
model = AutoModelForCausalLM.from_pretrained(model_id, device_map="cuda:0")
加载 AWQ 量化模型会自动将其他权重默认设置为 fp16 以提高性能。如果您想以不同格式加载这些其他权重,请使用torch_dtype
参数:
from transformers import AutoModelForCausalLM, AutoTokenizer
model_id = "TheBloke/zephyr-7B-alpha-AWQ"
model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.float32)
AWQ 量化也可以与 FlashAttention-2 结合,以进一步加速推理:
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained("TheBloke/zephyr-7B-alpha-AWQ", attn_implementation="flash_attention_2", device_map="cuda:0")
融合模块
融合模块提供了改进的准确性和性能,并且对于Llama和Mistral架构的 AWQ 模块支持开箱即用,但您也可以为不支持的架构融合 AWQ 模块。
融合模块不能与 FlashAttention-2 等其他优化技术结合使用。
支持的架构不支持的架构
要为支持的架构启用融合模块,请创建一个 AwqConfig 并设置参数fuse_max_seq_len
和do_fuse=True
。fuse_max_seq_len
参数是总序列长度,应包括上下文长度和预期生成长度。您可以将其设置为较大的值以确保安全。
例如,要融合TheBloke/Mistral-7B-OpenOrca-AWQ模型的 AWQ 模块。
import torch
from transformers import AwqConfig, AutoModelForCausalLM
model_id = "TheBloke/Mistral-7B-OpenOrca-AWQ"
quantization_config = AwqConfig(
bits=4,
fuse_max_seq_len=512,
do_fuse=True,
)
model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=quantization_config).to(0)
AutoGPTQ
在这个notebook中尝试使用 PEFT 进行 GPTQ 量化,并在这篇博客文章中了解更多细节!
AutoGPTQ库实现了 GPTQ 算法,这是一种后训练量化技术,其中权重矩阵的每一行都独立量化,以找到最小化误差的权重版本。这些权重被量化为 int4,但在推理过程中会动态恢复为 fp16。这可以通过 4 倍减少内存使用,因为 int4 权重在融合内核中而不是 GPU 的全局内存中被解量化,您还可以期望推理速度提升,因为使用较低的位宽需要更少的通信时间。
开始之前,请确保安装了以下库:
pip install auto-gptq
pip install git+https://github.com/huggingface/optimum.git
pip install git+https://github.com/huggingface/transformers.git
pip install --upgrade accelerate
要量化一个模型(目前仅支持文本模型),您需要创建一个 GPTQConfig 类,并设置要量化的位数、用于校准权重的数据集以及准备数据集的分词器。
from transformers import AutoModelForCausalLM, AutoTokenizer, GPTQConfig
model_id = "facebook/opt-125m"
tokenizer = AutoTokenizer.from_pretrained(model_id)
gptq_config = GPTQConfig(bits=4, dataset="c4", tokenizer=tokenizer)
您还可以将自己的数据集作为字符串列表传递,但强烈建议使用 GPTQ 论文中相同的数据集。
dataset = ["auto-gptq is an easy-to-use model quantization library with user-friendly apis, based on GPTQ algorithm."]
gptq_config = GPTQConfig(bits=4, dataset=dataset, tokenizer=tokenizer)
加载一个要量化的模型,并将gptq_config
传递给 from_pretrained()方法。设置device_map="auto"
以自动将模型卸载到 CPU,以帮助将模型适配到内存中,并允许模型模块在 CPU 和 GPU 之间移动以进行量化。
quantized_model = AutoModelForCausalLM.from_pretrained(model_id, device_map="auto", quantization_config=gptq_config)
如果由于数据集过大而导致内存不足,不支持磁盘卸载。如果是这种情况,请尝试传递max_memory
参数来分配设备(GPU 和 CPU)上要使用的内存量:
quantized_model = AutoModelForCausalLM.from_pretrained(model_id, device_map="auto", max_memory={0: "30GiB", 1: "46GiB", "cpu": "30GiB"}, quantization_config=gptq_config)
根据您的硬件,从头开始量化一个模型可能需要一些时间。在免费的 Google Colab GPU 上,量化 faceboook/opt-350m 模型可能需要约 5 分钟,但在 NVIDIA A100 上,量化一个 175B 参数模型可能需要约 4 小时。在量化模型之前,最好先检查 Hub,看看模型的 GPTQ 量化版本是否已经存在。
一旦您的模型被量化,您可以将模型和分词器推送到 Hub,这样可以轻松共享和访问。使用 push_to_hub()方法保存 GPTQConfig:
quantized_model.push_to_hub("opt-125m-gptq")
tokenizer.push_to_hub("opt-125m-gptq")
您还可以使用 save_pretrained()方法将您的量化模型保存在本地。如果模型是使用device_map
参数量化的,请确保在保存之前将整个模型移动到 GPU 或 CPU。例如,要在 CPU 上保存模型:
quantized_model.save_pretrained("opt-125m-gptq")
tokenizer.save_pretrained("opt-125m-gptq")
# if quantized with device_map set
quantized_model.to("cpu")
quantized_model.save_pretrained("opt-125m-gptq")
使用 from_pretrained()方法重新加载一个量化模型,并设置device_map="auto"
以自动将模型分布在所有可用的 GPU 上,以便更快地加载模型而不使用比所需更多的内存。
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("{your_username}/opt-125m-gptq", device_map="auto")
ExLlama
ExLlama是 Llama 模型的 Python/C++/CUDA 实现,旨在通过 4 位 GPTQ 权重实现更快的推理(查看这些基准测试)。当您创建一个 GPTQConfig 对象时,默认情况下会激活 ExLlama 内核。为了进一步提高推理速度,请配置exllama_config
参数使用ExLlamaV2内核:
import torch
from transformers import AutoModelForCausalLM, GPTQConfig
gptq_config = GPTQConfig(bits=4, exllama_config={"version":2})
model = AutoModelForCausalLM.from_pretrained("{your_username}/opt-125m-gptq", device_map="auto", quantization_config=gptq_config)
仅支持 4 位模型,并且我们建议在对量化模型进行微调时停用 ExLlama 内核。
只有当整个模型在 GPU 上时才支持 ExLlama 内核。如果您在 CPU 上使用 AutoGPTQ(版本>0.4.2)进行推理,则需要禁用 ExLlama 内核。这将覆盖 config.json 文件中与 ExLlama 内核相关的属性。
import torch
from transformers import AutoModelForCausalLM, GPTQConfig
gptq_config = GPTQConfig(bits=4, use_exllama=False)
model = AutoModelForCausalLM.from_pretrained("{your_username}/opt-125m-gptq", device_map="cpu", quantization_config=gptq_config)
bitsandbytes
bitsandbytes是将模型量化为 8 位和 4 位的最简单选择。8 位量化将 fp16 中的异常值与 int8 中的非异常值相乘,将非异常值转换回 fp16,然后将它们相加以返回 fp16 中的权重。这减少了异常值对模型性能的降级影响。4 位量化进一步压缩模型,通常与QLoRA一起用于微调量化的 LLM。
要使用 bitsandbytes,请确保已安装以下库:
8 位 4 位
pip install transformers accelerate bitsandbytes>0.37.0
现在您可以使用 from_pretrained()方法中的load_in_8bit
或load_in_4bit
参数来量化模型。只要模型支持使用 Accelerate 加载并包含torch.nn.Linear
层,这对任何模态的模型都适用。
8 位 4 位
将模型量化为 8 位可以减半内存使用量,对于大型模型,设置device_map="auto"
以有效地使用可用的 GPU:
from transformers import AutoModelForCausalLM
model_8bit = AutoModelForCausalLM.from_pretrained("bigscience/bloom-1b7", device_map="auto", load_in_8bit=True)
默认情况下,所有其他模块(如torch.nn.LayerNorm
)都会转换为torch.float16
。如果需要,您可以使用torch_dtype
参数更改这些模块的数据类型:
import torch
from transformers import AutoModelForCausalLM
model_8bit = AutoModelForCausalLM.from_pretrained("facebook/opt-350m", load_in_8bit=True, torch_dtype=torch.float32)
model_8bit.model.decoder.layers[-1].final_layer_norm.weight.dtype
一旦模型被量化为 8 位,除非您使用最新版本的 Transformers 和 bitsandbytes,否则无法将量化的权重推送到 Hub。如果您有最新版本,则可以使用 push_to_hub()方法将 8 位模型推送到 Hub。首先推送量化的 config.json 文件,然后是量化的模型权重。
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained("bigscience/bloom-560m", device_map="auto", load_in_8bit=True)
tokenizer = AutoTokenizer.from_pretrained("bigscience/bloom-560m")
model.push_to_hub("bloom-560m-8bit")
仅支持使用 8 位和 4 位权重进行训练额外参数。
您可以使用get_memory_footprint
方法检查内存占用情况:
print(model.get_memory_footprint())
可以从 from_pretrained()方法中加载量化模型,无需指定load_in_8bit
或load_in_4bit
参数:
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained("{your_username}/bloom-560m-8bit", device_map="auto")
8 位
在这篇博客文章中了解更多关于 8 位量化的细节!
本节探讨了 8 位模型的一些特定功能,如转移、异常值阈值、跳过模块转换和微调。
转移
8 位模型可以在 CPU 和 GPU 之间转移权重,以支持将非常大的模型适配到内存中。发送到 CPU 的权重实际上是以float32存储的,并且不会转换为 8 位。例如,要为bigscience/bloom-1b7模型启用转移,请首先创建一个 BitsAndBytesConfig:
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
quantization_config = BitsAndBytesConfig(llm_int8_enable_fp32_cpu_offload=True)
设计一个自定义设备映射,将所有内容都适配到 GPU 上,除了lm_head
,这部分将发送到 CPU:
device_map = {
"transformer.word_embeddings": 0,
"transformer.word_embeddings_layernorm": 0,
"lm_head": "cpu",
"transformer.h": 0,
"transformer.ln_f": 0,
}
现在使用自定义的device_map
和quantization_config
加载您的模型:
model_8bit = AutoModelForCausalLM.from_pretrained(
"bigscience/bloom-1b7",
device_map=device_map,
quantization_config=quantization_config,
)
异常值阈值
“异常值”是大于某个阈值的隐藏状态值,这些值是在 fp16 中计算的。虽然这些值通常是正态分布的([-3.5, 3.5]),但对于大型模型([-60, 6]或[6, 60]),这种分布可能会有很大不同。8 位量化适用于值约为 5,但超过这个值,会有显著的性能损失。一个很好的默认阈值是 6,但对于更不稳定的模型(小模型或微调),可能需要更低的阈值。
为了找到您的模型的最佳阈值,我们建议尝试在 BitsAndBytesConfig 中使用llm_int8_threshold
参数进行实验:
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
model_id = "bigscience/bloom-1b7"
quantization_config = BitsAndBytesConfig(
llm_int8_threshold=10,
)
model_8bit = AutoModelForCausalLM.from_pretrained(
model_id,
device_map=device_map,
quantization_config=quantization_config,
)
跳过模块转换
对于一些模型,如 Jukebox,您不需要将每个模块量化为 8 位,这实际上可能会导致不稳定性。对于 Jukebox,有几个lm_head
模块应该使用 BitsAndBytesConfig 中的llm_int8_skip_modules
参数跳过:
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
model_id = "bigscience/bloom-1b7"
quantization_config = BitsAndBytesConfig(
llm_int8_skip_modules=["lm_head"],
)
model_8bit = AutoModelForCausalLM.from_pretrained(
model_id,
device_map="auto",
quantization_config=quantization_config,
)
微调
使用PEFT库,您可以使用 8 位量化微调大型模型,如flan-t5-large和facebook/opt-6.7b。在训练时不需要传递device_map
参数,因为它会自动将您的模型加载到 GPU 上。但是,如果您想要,仍然可以使用device_map
参数自定义设备映射(device_map="auto"
仅应用于推断)。
4 位
在这个notebook中尝试 4 位量化,并在这篇博客文章中了解更多细节。
本节探讨了 4 位模型的一些特定功能,如更改计算数据类型、使用 Normal Float 4 (NF4)数据类型和使用嵌套量化。
计算数据类型
为了加速计算,您可以将数据类型从 float32(默认值)更改为 bf16,使用 BitsAndBytesConfig 中的bnb_4bit_compute_dtype
参数:
import torch
from transformers import BitsAndBytesConfig
quantization_config = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16)
Normal Float 4 (NF4)
NF4 是来自QLoRA论文的 4 位数据类型,适用于从正态分布初始化的权重。您应该使用 NF4 来训练 4 位基础模型。这可以通过 BitsAndBytesConfig 中的bnb_4bit_quant_type
参数进行配置:
from transformers import BitsAndBytesConfig
nf4_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
)
model_nf4 = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=nf4_config)
对于推断,bnb_4bit_quant_type
对性能没有太大影响。但是,为了保持与模型权重一致,您应该使用bnb_4bit_compute_dtype
和torch_dtype
值。
嵌套量化
嵌套量化是一种技术,可以在不增加性能成本的情况下节省额外的内存。此功能对已经量化的权重执行第二次量化,以节省额外的 0.4 位/参数。例如,使用嵌套量化,您可以在 16GB 的 NVIDIA T4 GPU 上微调Llama-13b模型,序列长度为 1024,批量大小为 1,并启用梯度累积 4 步。
from transformers import BitsAndBytesConfig
double_quant_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
)
model_double_quant = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-13b", quantization_config=double_quant_config)
Optimum
Optimum库支持 Intel、Furiosa、ONNX Runtime、GPTQ 和较低级别的 PyTorch 量化功能。如果您正在使用像 Intel CPU、Furiosa NPU 或像 ONNX Runtime 这样的模型加速器这样的特定和优化的硬件,请考虑使用 Optimum 进行量化。
基准测试
要比较每种量化方案的速度、吞吐量和延迟,请查看从optimum-benchmark库获得的以下基准测试。该基准测试在 NVIDIA A1000 上运行,用于TheBloke/Mistral-7B-v0.1-AWQ和TheBloke/Mistral-7B-v0.1-GPTQ模型。这些还与 bitsandbytes 量化方法以及本机 fp16 模型进行了测试。
前向峰值内存/批处理大小
每批生成峰值内存/批处理大小
每批生成吞吐量/批处理大小
前向延迟/批处理大小
基准测试表明,AWQ 量化在推理、文本生成方面是最快的,并且在文本生成方面具有最低的峰值内存。然而,AWQ 在每个批处理大小上具有最大的前向延迟。要了解每种量化方法的优缺点的更详细讨论,请阅读🤗 Transformers 中本地支持的量化方案概述博客文章。
融合 AWQ 模块
TheBloke/Mistral-7B-OpenOrca-AWQ模型在batch_size=1
下进行了基准测试,有无融合模块。
未融合模块
批处理大小 | 预填充长度 | 解码长度 | 预填充标记/秒 | 解码标记/秒 | 内存(VRAM) |
---|---|---|---|---|---|
1 | 32 | 32 | 60.0984 | 38.4537 | 4.50 GB (5.68%) |
1 | 64 | 64 | 1333.67 | 31.6604 | 4.50 GB (5.68%) |
1 | 128 | 128 | 2434.06 | 31.6272 | 4.50 GB (5.68%) |
1 | 256 | 256 | 3072.26 | 38.1731 | 4.50 GB (5.68%) |
1 | 512 | 512 | 3184.74 | 31.6819 | 4.59 GB (5.80%) |
1 | 1024 | 1024 | 3148.18 | 36.8031 | 4.81 GB (6.07%) |
1 | 2048 | 2048 | 2927.33 | 35.2676 | 5.73 GB (7.23%) |
融合模块
批处理大小 | 预填充长度 | 解码长度 | 预填充标记/秒 | 解码标记/秒 | 内存(VRAM) |
---|---|---|---|---|---|
1 | 32 | 32 | 81.4899 | 80.2569 | 4.00 GB (5.05%) |
1 | 64 | 64 | 1756.1 | 106.26 | 4.00 GB (5.05%) |
1 | 128 | 128 | 2479.32 | 105.631 | 4.00 GB (5.06%) |
1 | 256 | 256 | 1813.6 | 85.7485 | 4.01 GB (5.06%) |
1 | 512 | 512 | 2848.9 | 97.701 | 4.11 GB (5.19%) |
1 | 1024 | 1024 | 3044.35 | 87.7323 | 4.41 GB (5.57%) |
1 | 2048 | 2048 | 2715.11 | 89.4709 | 5.57 GB (7.04%) |
融合和未融合模块的速度和吞吐量也经过了optimum-benchmark库的测试。
前向峰值内存/批处理大小
每批生成吞吐量/批处理大小
高效的训练技巧
在单个 GPU 上进行高效训练的方法和工具
原文链接:
huggingface.co/docs/transformers/v4.37.2/en/perf_train_gpu_one
本指南演示了您可以使用的实用技术,通过优化内存利用率、加快训练速度或两者兼而有之来提高模型训练的效率。如果您想了解 GPU 在训练过程中的使用情况,请先参考模型训练解剖概念指南。本指南侧重于实用技术。
如果您可以访问具有多个 GPU 的计算机,这些方法仍然有效,此外,您还可以利用多 GPU 部分中概述的其他方法。
在训练大型模型时,应同时考虑两个方面:
-
数据吞吐量/训练时间
-
模型性能
最大化吞吐量(样本/秒)可以降低训练成本。通常通过尽可能充分利用 GPU 并将 GPU 内存填满来实现这一目标。如果所需的批处理大小超出了 GPU 内存的限制,可以使用内存优化技术,如梯度累积,来帮助。
然而,如果首选的批处理大小适合内存,就没有理由应用内存优化技术,因为它们可能会减慢训练速度。仅仅因为可以使用大批处理大小,并不一定意味着应该这样做。作为超参数调整的一部分,您应该确定哪种批处理大小产生最佳结果,然后相应地优化资源。
本指南涵盖的方法和工具可以根据它们对训练过程的影响进行分类:
方法/工具 | 提高训练速度 | 优化内存利用率 |
---|---|---|
批处理大小选择 | 是 | 是 |
梯度累积 | 否 | 是 |
梯度检查点 | 否 | 是 |
混合精度训练 | 是 | (否) |
优化器选择 | 是 | 是 |
数据预加载 | 是 | 否 |
DeepSpeed Zero | 否 | 是 |
torch.compile | 是 | 否 |
参数高效微调(PEFT) | 否 | 是 |
注意:当使用混合精度与小模型和大批处理大小时,会有一些内存节省,但对于大模型和小批处理大小,内存使用量会更大。
您可以结合上述方法以获得累积效果。这些技术对您都是可用的,无论您是使用 Trainer 训练模型,还是编写纯 PyTorch 循环,在这种情况下,您可以通过使用🤗 Accelerate 配置这些优化。
如果这些方法没有带来足够的收益,您可以探索以下选项:
-
考虑构建自己的自定义 Docker 容器,其中包含高效的软件预构建
-
考虑使用专家混合(MoE)的模型
-
将您的模型转换为 BetterTransformer 以利用 PyTorch 原生注意力
最后,如果即使切换到像 A100 这样的服务器级 GPU 后仍然不够,考虑切换到多 GPU 设置。所有这些方法在多 GPU 设置中仍然有效,此外,您还可以利用多 GPU 部分中概述的其他并行技术。
批处理大小选择
为了实现最佳性能,首先要确定适当的批处理大小。建议使用大小为 2^N 的批处理大小和输入/输出神经元计数。通常是 8 的倍数,但可以更高,具体取决于所使用的硬件和模型的数据类型。
有关参考,请查看 NVIDIA 关于全连接层(涉及 GEMMs(通用矩阵乘法))的输入/输出神经元计数和批次大小的建议。
Tensor Core 要求根据数据类型和硬件定义乘数。例如,对于 fp16 数据类型,推荐使用 8 的倍数,除非是 A100 GPU,此时使用 64 的倍数。
对于较小的参数,还要考虑维度量化效应。这是瓦片化发生的地方,正确的乘数可以显著加快速度。
梯度累积
梯度累积方法旨在以较小的增量计算梯度,而不是一次为整个批次计算梯度。这种方法涉及通过模型执行前向和反向传递,并在过程中累积梯度来迭代计算较小批次的梯度。一旦积累了足够数量的梯度,就会执行模型的优化步骤。通过使用梯度累积,可以将有效批次大小增加到 GPU 内存容量所施加的限制之外。然而,需要注意的是,梯度累积引入的额外前向和反向传递可能会减慢训练过程。
您可以通过向 TrainingArguments 添加gradient_accumulation_steps
参数来启用梯度累积:
training_args = TrainingArguments(per_device_train_batch_size=1, gradient_accumulation_steps=4, **default_args)
在上述示例中,您的有效批次大小变为 4。
或者,使用🤗 Accelerate 来完全控制训练循环。在本指南的后面找到🤗 Accelerate 示例。
尽可能地最大化 GPU 使用率是建议的,但是高数量的梯度累积步骤可能会导致训练减速更加明显。考虑以下示例。假设per_device_train_batch_size=4
,没有梯度累积达到了 GPU 的极限。如果您想要使用大小为 64 的批次进行训练,请不要将per_device_train_batch_size
设置为 1,并将gradient_accumulation_steps
设置为 64。相反,保持per_device_train_batch_size=4
,并设置gradient_accumulation_steps=16
。这样可以实现相同的有效批次大小,同时更好地利用可用的 GPU 资源。
有关更多信息,请参考RTX-3090和A100的批次大小和梯度累积基准。
梯度检查点
即使将批次大小设置为 1 并使用梯度累积,一些大型模型仍可能面临内存问题。这是因为还有其他组件也需要内存存储。
保存前向传递中的所有激活以便在反向传递期间计算梯度可能会导致显着的内存开销。另一种方法是在反向传递期间丢弃激活并在需要时重新计算它们,这将引入相当大的计算开销并减慢训练过程。
梯度检查点提供了这两种方法之间的折衷方案,并在计算图中保存了策略性选择的激活,因此只需重新计算一小部分激活以获得梯度。有关梯度检查点的深入解释,请参阅这篇很棒的文章。
要在 Trainer 中启用梯度检查点,请将相应的标志传递给 TrainingArguments:
training_args = TrainingArguments(
per_device_train_batch_size=1, gradient_accumulation_steps=4, gradient_checkpointing=True, **default_args
)
或者,使用🤗 Accelerate-在本指南中找到🤗 Accelerate 示例(#使用加速)。
虽然梯度检查点可能提高内存效率,但会使训练速度减慢约 20%。
混合精度训练
混合精度训练是一种旨在通过利用较低精度数值格式来处理某些变量来优化训练模型的计算效率的技术。传统上,大多数模型使用 32 位浮点精度(fp32 或 float32)来表示和处理变量。然而,并非所有变量都需要这种高精度级别才能获得准确的结果。通过将某些变量的精度降低到较低的数值格式,如 16 位浮点(fp16 或 float16),我们可以加快计算速度。因为在这种方法中,一些计算是以半精度进行的,而一些仍然是以全精度进行的,所以这种方法被称为混合精度训练。
最常见的混合精度训练是通过使用 fp16(float16)数据类型来实现的,但是一些 GPU 架构(例如 Ampere 架构)提供了 bf16 和 tf32(CUDA 内部数据类型)数据类型。查看NVIDIA 博客以了解这些数据类型之间的区别。
fp16
混合精度训练的主要优势来自于将激活保存在半精度(fp16)中。尽管梯度也是以半精度计算的,但它们在优化步骤中被转换回全精度,因此在这里没有节省内存。虽然混合精度训练可以加快计算速度,但也可能导致更多的 GPU 内存被利用,特别是对于小批量大小。这是因为模型现在以 16 位和 32 位精度(GPU 上原始模型的 1.5 倍)存在于 GPU 上。
要启用混合精度训练,请将fp16
标志设置为True
:
training_args = TrainingArguments(per_device_train_batch_size=4, fp16=True, **default_args)
如果您更喜欢使用🤗 Accelerate,请在本指南中找到🤗 Accelerate 示例(#使用加速)。
BF16
如果您可以访问 Ampere 或更新的硬件,您可以使用 bf16 进行混合精度训练和评估。虽然 bf16 的精度比 fp16 差,但它具有更大的动态范围。在 fp16 中,您可以拥有的最大数字是65535
,任何超过这个数字的数字都会导致溢出。bf16 的数字可以达到3.39e+38
(!),这大约与 fp32 相同-因为两者都使用了 8 位用于数值范围。
您可以在🤗 Trainer 中启用 BF16:
training_args = TrainingArguments(bf16=True, **default_args)
TF32
Ampere 硬件使用一种名为 tf32 的神奇数据类型。它具有与 fp32 相同的数值范围(8 位),但是精度只有 10 位(与 fp16 相同),总共只使用了 19 位。它在这种意义上是“神奇的”,即您可以使用正常的 fp32 训练和/或推理代码,并通过启用 tf32 支持,您可以获得高达 3 倍的吞吐量改进。您只需要将以下内容添加到您的代码中:
import torch
torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = True
CUDA 将在可能的情况下自动切换到使用 tf32 而不是 fp32,假设所使用的 GPU 来自 Ampere 系列。
根据NVIDIA 研究,大多数机器学习训练工作负载显示出与 fp32 相同的困惑度和收敛性。如果您已经在使用 fp16 或 bf16 混合精度,这也可能有助于提高吞吐量。
您可以在🤗 Trainer 中启用此模式:
TrainingArguments(tf32=True, **default_args)
tf32 无法通过tensor.to(dtype=torch.tf32)
直接访问,因为它是内部 CUDA 数据类型。您需要torch>=1.7
才能使用 tf32 数据类型。
有关 tf32 与其他精度的更多信息,请参考以下基准测试:RTX-3090和A100。
Flash Attention 2
您可以通过在 transformers 中使用 Flash Attention 2 集成来加快训练吞吐量。查看单 GPU 部分中的适当部分,了解如何加载带有 Flash Attention 2 模块的模型的更多信息。
优化器选择
用于训练变压器模型的最常用优化器是 Adam 或 AdamW(带有权重衰减的 Adam)。Adam 通过存储先前梯度的滚动平均值实现良好的收敛;然而,它会增加与模型参数数量相同数量级的额外内存占用。为了解决这个问题,您可以使用另一种优化器。例如,如果您在 NVIDIA GPU 上安装了NVIDIA/apex,或者在 AMD GPU 上安装了ROCmSoftwarePlatform/apex,adamw_apex_fused
将为您提供所有支持的 AdamW 优化器中最快的训练体验。
Trainer 集成了各种可立即使用的优化器:adamw_hf
、adamw_torch
、adamw_torch_fused
、adamw_apex_fused
、adamw_anyprecision
、adafactor
或adamw_bnb_8bit
。更多优化器可以通过第三方实现插入。
让我们更仔细地看看两种替代 AdamW 优化器:
-
adafactor
可在 Trainer 中使用 -
adamw_bnb_8bit
也可在 Trainer 中使用,但以下提供了第三方集成以供演示。
以 3B 参数模型“t5-3b”为例进行比较:
-
标准的 AdamW 优化器将需要 24GB 的 GPU 内存,因为它为每个参数使用 8 字节(8*3 => 24GB)
-
Adafactor 优化器将需要超过 12GB。它为每个参数使用略多于 4 字节,因此 4*3,然后再加一些。
-
8 位 BNB 量化优化器将仅使用(2*3)6GB,如果所有优化器状态都被量化。
Adafactor
Adafactor 不会为权重矩阵中的每个元素存储滚动平均值。相反,它保留聚合信息(按行和列的滚动平均和),显著减少了内存占用。然而,与 Adam 相比,Adafactor 在某些情况下可能收敛较慢。
您可以通过在 TrainingArguments 中设置optim="adafactor"
来切换到 Adafactor:
training_args = TrainingArguments(per_device_train_batch_size=4, optim="adafactor", **default_args)
结合其他方法(梯度累积、梯度检查点和混合精度训练),您可以在保持吞吐量的同时实现高达 3 倍的改进!然而,如前所述,Adafactor 的收敛性可能比 Adam 更差。
8 位 Adam
与 Adafactor 等聚合优化器状态不同,8 位 Adam 保留完整状态并对其进行量化。量化意味着以较低精度存储状态,并仅在优化时对其进行反量化。这类似于混合精度训练的思想。
要使用adamw_bnb_8bit
,您只需在 TrainingArguments 中设置optim="adamw_bnb_8bit"
:
training_args = TrainingArguments(per_device_train_batch_size=4, optim="adamw_bnb_8bit", **default_args)
然而,我们也可以使用第三方实现的 8 位优化器进行演示,以了解如何集成。
首先,按照 GitHub repo中的安装指南安装实现 8 位 Adam 优化器的bitsandbytes
库。
接下来需要初始化优化器。这涉及两个步骤:
-
首先,将模型的参数分为两组 - 一组应用权重衰减,另一组不应用。通常,偏置和层归一化参数不会被权重衰减。
-
然后进行一些参数整理,以使用与先前使用的 AdamW 优化器相同的参数。
import bitsandbytes as bnb
from torch import nn
from transformers.trainer_pt_utils import get_parameter_names
training_args = TrainingArguments(per_device_train_batch_size=4, **default_args)
decay_parameters = get_parameter_names(model, [nn.LayerNorm])
decay_parameters = [name for name in decay_parameters if "bias" not in name]
optimizer_grouped_parameters = [
{
"params": [p for n, p in model.named_parameters() if n in decay_parameters],
"weight_decay": training_args.weight_decay,
},
{
"params": [p for n, p in model.named_parameters() if n not in decay_parameters],
"weight_decay": 0.0,
},
]
optimizer_kwargs = {
"betas": (training_args.adam_beta1, training_args.adam_beta2),
"eps": training_args.adam_epsilon,
}
optimizer_kwargs["lr"] = training_args.learning_rate
adam_bnb_optim = bnb.optim.Adam8bit(
optimizer_grouped_parameters,
betas=(training_args.adam_beta1, training_args.adam_beta2),
eps=training_args.adam_epsilon,
lr=training_args.learning_rate,
)
最后,将自定义优化器作为参数传递给Trainer
:
trainer = Trainer(model=model, args=training_args, train_dataset=ds, optimizers=(adam_bnb_optim, None))
结合其他方法(梯度累积、梯度检查点和混合精度训练),您可以期望获得大约 3 倍的内存改进,甚至比使用 Adafactor 时的吞吐量稍高。
multi_tensor
pytorch-nightly 引入了torch.optim._multi_tensor
,应该显着加快具有大量小特征张量的优化器的速度。最终应该成为默认设置,但如果您想更早尝试它,请查看这个 GitHub 问题。
数据预加载
达到良好训练速度的一个重要要求是能够以 GPU 能够处理的最大速度提供数据。默认情况下,所有操作都在主进程中进行,可能无法快速从磁盘读取数据,从而导致瓶颈,导致 GPU 利用率不足。配置以下参数以减少瓶颈:
-
DataLoader(pin_memory=True, ...)
- 确保数据预加载到 CPU 上的固定内存中,通常会导致从 CPU 到 GPU 内存的传输速度更快。 -
DataLoader(num_workers=4, ...)
- 生成几个工作进程以更快地预加载数据。在训练过程中,观察 GPU 利用率统计数据;如果远离 100%,尝试增加工作进程的数量。当然,问题可能出在其他地方,因此许多工作进程不一定会导致更好的性能。
在使用 Trainer 时,相应的 TrainingArguments 是:dataloader_pin_memory
(默认为True
),和dataloader_num_workers
(默认为0
)。
DeepSpeed ZeRO
DeepSpeed 是一个开源的深度学习优化库,与🤗 Transformers 和🤗 Accelerate 集成。它提供了各种功能和优化,旨在提高大规模深度学习训练的效率和可扩展性。
如果您的模型适合单个 GPU 并且有足够的空间来容纳小批量大小,则不需要使用 DeepSpeed,因为它只会减慢速度。但是,如果模型无法适应单个 GPU 或无法容纳小批量,则可以利用 DeepSpeed ZeRO + CPU Offload,或 NVMe Offload 来处理更大的模型。在这种情况下,您需要单独安装该库,然后按照指南之一创建配置文件并启动 DeepSpeed:
-
有关 DeepSpeed 与 Trainer 集成的详细指南,请查看相应文档,特别是单个 GPU 部署部分。在笔记本中使用 DeepSpeed 需要进行一些调整;请查看相应指南。
-
如果您更喜欢使用🤗 Accelerate,请参考🤗 Accelerate DeepSpeed 指南。
使用 torch.compile
PyTorch 2.0 引入了一个新的编译函数,不需要对现有的 PyTorch 代码进行任何修改,只需添加一行代码即可优化您的代码:model = torch.compile(model)
。
如果使用 Trainer,您只需要在 TrainingArguments 中传递torch_compile
选项:
training_args = TrainingArguments(torch_compile=True, **default_args)
torch.compile
使用 Python 的帧评估 API 自动从现有的 PyTorch 程序创建图。在捕获图之后,可以部署不同的后端以将图降低到优化引擎。您可以在PyTorch 文档中找到更多详细信息和基准测试。
torch.compile
有一个不断增长的后端列表,可以通过调用torchdynamo.list_backends()
找到,每个后端都有其可选依赖项。
通过在 TrainingArguments 中指定torch_compile_backend
来选择要使用的后端。一些最常用的后端包括:
调试后端:
-
dynamo.optimize("eager")
- 使用 PyTorch 运行提取的 GraphModule。这在调试 TorchDynamo 问题时非常有用。 -
dynamo.optimize("aot_eager")
- 使用 AotAutograd 而不使用编译器,即仅使用 PyTorch eager 进行 AotAutograd 的提取前向和反向图。这对调试很有用,但不太可能提供加速。
训练和推理后端:
-
dynamo.optimize("inductor")
- 使用 TorchInductor 后端,通过利用 codegened Triton 内核实现 AotAutograd 和 cudagraphs。阅读更多 -
dynamo.optimize("nvfuser")
- nvFuser 与 TorchScript。阅读更多 -
dynamo.optimize("aot_nvfuser")
- nvFuser 与 AotAutograd。阅读更多 -
dynamo.optimize("aot_cudagraphs")
- 使用 AotAutograd 的 cudagraphs。阅读更多
仅推理后端:
-
dynamo.optimize("ofi")
- 使用 Torchscript 的 optimize_for_inference。阅读更多 -
dynamo.optimize("fx2trt")
- 使用 NVIDIA TensorRT 进行推理优化。阅读更多 -
dynamo.optimize("onnxrt")
- 使用 ONNXRT 进行 CPU/GPU 推理。阅读更多 -
dynamo.optimize("ipex")
- 使用 IPEX 进行 CPU 推理。阅读更多
使用torch.compile
与🤗 Transformers 的示例,请查看这篇博文,介绍如何使用最新的 PyTorch 2.0 功能微调 BERT 模型进行文本分类
使用🤗 PEFT
参数高效微调(PEFT)方法在微调期间冻结预训练模型参数,并在其上添加少量可训练参数(适配器)。
因此,与优化器状态和梯度相关的内存大大减少。
例如,对于普通的 AdamW,优化器状态的内存需求将是:
-
fp32 参数的副本:4 字节/参数
-
动量:4 字节/参数
-
方差:4 字节/参数
假设一个具有 70 亿参数和 2 亿参数注入低秩适配器的模型。
普通模型的优化器状态的内存需求将为 12 * 7 = 84 GB(假设有 7B 可训练参数)。
添加 Lora 会略微增加与模型权重相关的内存,并大幅减少优化器状态的内存需求至 12 * 0.2 = 2.4GB。
在PEFT 文档或PEFT 存储库中详细了解 PEFT 及其详细用法。
使用🤗 Accelerate
使用🤗 Accelerate可以在完全控制训练循环的同时使用上述方法,并且基本上可以使用纯 PyTorch 编写循环并进行一些微小修改。
假设您已将 TrainingArguments 中的方法组合如下:
training_args = TrainingArguments(
per_device_train_batch_size=1,
gradient_accumulation_steps=4,
gradient_checkpointing=True,
fp16=True,
**default_args,
)
使用🤗 Accelerate 的完整示例训练循环只有几行代码:
from accelerate import Accelerator
from torch.utils.data.dataloader import DataLoader
dataloader = DataLoader(ds, batch_size=training_args.per_device_train_batch_size)
if training_args.gradient_checkpointing:
model.gradient_checkpointing_enable()
accelerator = Accelerator(fp16=training_args.fp16)
model, optimizer, dataloader = accelerator.prepare(model, adam_bnb_optim, dataloader)
model.train()
for step, batch in enumerate(dataloader, start=1):
loss = model(**batch).loss
loss = loss / training_args.gradient_accumulation_steps
accelerator.backward(loss)
if step % training_args.gradient_accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()
首先,我们将数据集包装在DataLoader
中。然后,我们可以通过调用模型的 gradient_checkpointing_enable()方法启用梯度检查点。当我们初始化Accelerator
时,我们可以指定是否要使用混合精度训练,并且它将在prepare
调用中为我们处理。在prepare
调用期间,如果我们使用多个 GPU,数据加载器也将分布在工作进程之间。我们使用与之前示例相同的 8 位优化器。
最后,我们可以添加主要的训练循环。请注意,backward
调用由🤗 Accelerate 处理。我们还可以看到梯度累积的工作原理:我们规范化损失,因此在累积结束时获得平均值,一旦我们有足够的步骤,我们就运行优化。
使用🤗 Accelerate 实现这些优化技术只需要几行代码,并且在训练循环中具有更大的灵活性。要查看所有功能的完整文档,请查看Accelerate 文档。
高效的软件预构建
PyTorch 的pip 和 conda 构建预先构建了 cuda 工具包,足以运行 PyTorch,但如果需要构建 cuda 扩展,则不足。
有时,可能需要额外的努力来预构建一些组件。例如,如果您使用的是未经预编译的库,如apex
。在其他情况下,弄清楚如何在系统范围内安装正确的 cuda 工具包可能会很复杂。为了解决这些情况,PyTorch 和 NVIDIA 发布了一个新版本的 NGC docker 容器,其中已经预先构建了一切。您只需在其中安装您的程序,它就可以立即运行。
这种方法在您想要调整 pytorch 源代码和/或制作新的定制构建时也很有用。要找到您想要的 docker 镜像版本,请从PyTorch 发布说明开始,选择最新的一个月发布之一。进入所需发布的发布说明,检查环境的组件是否符合您的需求(包括 NVIDIA 驱动程序要求!),然后在该文档的顶部转到相应的 NGC 页面。如果由于某种原因您迷失了方向,这里是所有 PyTorch NGC 镜像的索引。
接下来按照说明下载和部署 docker 镜像。
专家混合
一些最近的论文报告了 4-5 倍的训练加速和将 Mixture of Experts(MoE)集成到 Transformer 模型中以实现更快的推理。
由于发现更多的参数会导致更好的性能,这种技术允许将参数数量增加一个数量级,而不增加训练成本。
在这种方法中,每个其他的 FFN 层都被一个 MoE 层替换,该层由许多专家组成,具有一个门控函数,根据输入令牌在序列中的位置平衡地训练每个专家。
(来源:GLAM)
您可以在本节末尾列出的论文中找到详尽的细节和比较表。
这种方法的主要缺点是它需要大量的 GPU 内存 - 几乎比其密集等价物大一个数量级。提出了各种蒸馏和方法,以克服更高的内存需求。
然而,存在直接的权衡,您可以使用少量专家和 2-3 倍较小的基础模型,而不是数十或数百个专家,从而导致 5 倍较小的模型,因此适度增加训练速度,同时适度增加内存需求。
大多数相关论文和实现都是围绕 Tensorflow/TPUs 构建的:
对于 Pytorch,DeepSpeed 也构建了一个:DeepSpeed-MoE: 推进混合专家推理和训练以支持下一代 AI 规模,Mixture of Experts - 博文:1,2以及大型基于 Transformer 的自然语言生成模型的特定部署:博文,Megatron-Deepspeed 分支。
使用 PyTorch 原生注意力和 Flash Attention
PyTorch 2.0 发布了一个原生的torch.nn.functional.scaled_dot_product_attention
(SDPA),允许使用融合的 GPU 内核,如内存高效注意力和闪存注意力。
安装optimum
包后,可以替换相关的内部模块以使用 PyTorch 的原生注意力,方法如下:
model = model.to_bettertransformer()
转换后,像往常一样训练模型。
PyTorch 原生的scaled_dot_product_attention
操作符只有在没有提供attention_mask
时才能分派到 Flash Attention。
默认情况下,在训练模式下,BetterTransformer 集成取消了掩码支持,只能用于不需要填充掩码的批量训练。例如,在掩码语言建模或因果语言建模期间。BetterTransformer 不适用于需要填充掩码的任务的微调模型。
查看这篇博文,了解有关 SDPA 加速和节省内存的更多信息。
多 GPU 上的高效训练
原始文本:
huggingface.co/docs/transformers/v4.37.2/en/perf_train_gpu_many
如果在单个 GPU 上训练模型太慢或者模型的权重无法适应单个 GPU 的内存,则过渡到多 GPU 设置可能是一个可行的选择。在进行此过渡之前,彻底探索在单个 GPU 上进行高效训练的方法和工具中涵盖的所有策略,因为它们普遍适用于任意数量的 GPU 上的模型训练。一旦您采用了这些策略并发现它们在单个 GPU 上不足以满足您的情况时,请考虑转移到多个 GPU。
从单个 GPU 过渡到多个 GPU 需要引入某种形式的并行性,因为工作负载必须分布在资源之间。可以采用多种技术来实现并行性,例如数据并行性,张量并行性和管道并行性。重要的是要注意,没有一种大小适合所有的解决方案,最佳设置取决于您正在使用的特定硬件配置。
本指南提供了对各种并行性类型的深入概述,以及有关如何组合的指导
技术和选择适当的方法。有关分布式训练的逐步教程,请参考🤗加速文档。
尽管本指南讨论的主要概念可能适用于各种框架,但在这里我们专注于基于 PyTorch 的实现。
在深入研究每种技术的具体细节之前,让我们回顾一下在大型基础设施上训练大型模型时的大致决策过程。
可扩展性策略
首先估算训练模型所需的 vRAM 量。对于托管在🤗 Hub 上的模型,请使用我们的模型内存计算器,该计算器可以在几个百分点的范围内给出准确的计算结果。
单节点/多 GPU 设置的并行化策略
在单节点上使用多个 GPU 训练模型时,您选择的并行化策略可能会显著影响性能。以下是您的选择:
情况 1:您的模型适合单个 GPU
如果您的模型可以轻松适应单个 GPU,则有两个主要选项:
-
DDP - 分布式数据并行
-
ZeRO - 根据所使用的情况和配置,这种方法可能会更快,但是,值得尝试一下。
情况 2:您的模型不适合单个 GPU:
如果您的模型太大而无法适应单个 GPU,您有几种替代方案可考虑:
-
PipelineParallel(PP)
-
ZeRO
-
TensorParallel(TP)
具有非常快的节点间连接(例如 NVLINK 或 NVSwitch)时,所有三种策略(PP,ZeRO,TP)应该会产生类似的性能。但是,如果没有这些,PP 将比 TP 或 ZeRO 更快。 TP 的程度也可能会有所不同。最好根据您的特定设置进行实验,以确定最合适的策略。
TP 几乎总是在单个节点内使用。即 TP 大小<=每个节点的 GPU 数。
情况 3:您的模型最大层不适合单个 GPU
-
如果您没有使用 ZeRO,则必须使用 TensorParallel(TP),因为仅使用 PipelineParallel(PP)将无法容纳大层。
-
如果您使用 ZeRO,另外采用来自在单个 GPU 上进行高效训练的方法和工具的技术。
多节点/多 GPU 设置的并行化策略
-
当您具有快速的节点间连接(例如 NVLINK 或 NVSwitch)时,请考虑使用以下选项之一:
-
ZeRO - 因为它几乎不需要对模型进行修改
-
将 PipelineParallel(PP)与 TensorParallel(TP)和 DataParallel(DP)结合使用-这种方法将减少通信量,但需要对模型进行重大更改
-
-
当您的节点间连接速度慢且 GPU 内存不足时:
- 使用 DataParallel(DP) 与 PipelineParallel(PP)、TensorParallel(TP) 和 ZeRO 的组合。
在本指南的后续部分中,我们将深入探讨这些不同的并行方法是如何工作的。
数据并行
即使只有 2 个 GPU,您也可以充分利用 PyTorch 内置功能提供的加速训练能力,例如 DataParallel
(DP) 和 DistributedDataParallel
(DDP)。请注意,PyTorch 文档建议优先选择 DistributedDataParallel
(DDP) 而不是 DataParallel
(DP) 用于多 GPU 训练,因为它适用于所有模型。让我们看看这两种方法是如何工作的以及它们之间的区别。
DataParallel vs DistributedDataParallel
为了了解这两种方法之间的 GPU 间通信开销的关键差异,让我们回顾每批的过程:
DDP:
-
在开始时,主进程将模型从 GPU 0 复制到其他 GPU
-
然后对于每批:
-
每个 GPU 直接消耗其数据的小批量。
-
在
backward
过程中,一旦本地梯度准备就绪,它们将在所有进程中进行平均。
-
DP:
对于每批:
-
GPU 0 读取数据批次,然后将一个小批量发送到每个 GPU。
-
最新的模型从 GPU 0 复制到每个 GPU。
-
forward
被执行,每个 GPU 的输出被发送到 GPU 0 来计算损失。 -
损失从 GPU 0 分发到所有 GPU,并运行
backward
。 -
每个 GPU 的梯度被发送到 GPU 0 并进行平均。
关键差异包括:
-
DDP 每批只执行一次通信 - 发送梯度,而 DP 每批执行五次不同的数据交换。DDP 使用 torch.distributed 复制数据,而 DP 通过 Python 线程在进程内复制数据(这会引入与 GIL 相关的限制)。因此,
DistributedDataParallel
(DDP) 通常比DataParallel
(DP) 更快,除非您的 GPU 卡之间的连接速度很慢。 -
在 DP 下,GPU 0 执行的工作明显多于其他 GPU,导致 GPU 利用率不足。
-
DDP 支持跨多台机器的分布式训练,而 DP 不支持。
这并不是 DP 和 DDP 之间的所有差异的详尽列表,然而,其他细微差别超出了本指南的范围。您可以通过阅读这篇文章来更深入地了解这些方法。
让我们通过一个实验来说明 DP 和 DDP 之间的差异。我们将基准测试 DP 和 DDP 之间的差异,并增加 NVLink 存在的背景:
-
硬件:2x TITAN RTX 24GB 每个 + 带有 2 个 NVLink 的 NVlink(
nvidia-smi topo -m
中的NV2
)。 -
软件:
pytorch-1.8-to-be
+cuda-11.0
/transformers==4.3.0.dev0
。
为了在其中一个基准测试中禁用 NVLink 功能,我们使用 NCCL_P2P_DISABLE=1
。
这里是基准测试代码和输出:
DP
rm -r /tmp/test-clm; CUDA_VISIBLE_DEVICES=0,1 \
python examples/pytorch/language-modeling/run_clm.py \
--model_name_or_path gpt2 --dataset_name wikitext --dataset_config_name wikitext-2-raw-v1 \
--do_train --output_dir /tmp/test-clm --per_device_train_batch_size 4 --max_steps 200
{'train_runtime': 110.5948, 'train_samples_per_second': 1.808, 'epoch': 0.69}
DDP w/ NVlink
rm -r /tmp/test-clm; CUDA_VISIBLE_DEVICES=0,1 \
torchrun --nproc_per_node 2 examples/pytorch/language-modeling/run_clm.py \
--model_name_or_path gpt2 --dataset_name wikitext --dataset_config_name wikitext-2-raw-v1 \
--do_train --output_dir /tmp/test-clm --per_device_train_batch_size 4 --max_steps 200
{'train_runtime': 101.9003, 'train_samples_per_second': 1.963, 'epoch': 0.69}
DDP w/o NVlink
rm -r /tmp/test-clm; NCCL_P2P_DISABLE=1 CUDA_VISIBLE_DEVICES=0,1 \
torchrun --nproc_per_node 2 examples/pytorch/language-modeling/run_clm.py \
--model_name_or_path gpt2 --dataset_name wikitext --dataset_config_name wikitext-2-raw-v1 \
--do_train --output_dir /tmp/test-clm --per_device_train_batch_size 4 --max_steps 200
{'train_runtime': 131.4367, 'train_samples_per_second': 1.522, 'epoch': 0.69}
这里是为方便起见在表格中收集的相同基准测试结果:
类型 | NVlink | 时间 |
---|---|---|
2:DP | Y | 110 秒 |
2:DDP | Y | 101 秒 |
2:DDP | N | 131 秒 |
如您所见,在这种情况下,DP 比 DDP with NVlink 慢约 10%,但比 DDP without NVlink 快约 15%。真正的差异取决于每个 GPU 需要与其他 GPU 同步多少数据 - 同步的数据越多,慢速链接就会阻碍整体运行时间。
ZeRO 数据并行
ZeRO 动力数据并行(ZeRO-DP)在这篇博文中有详细说明。
虽然看起来复杂,但这与DataParallel
(DP)非常相似。不同之处在于,每个 GPU 只存储其一部分,而不是复制完整的模型参数、梯度和优化器状态。然后,在运行时,当需要完整的层参数时,所有 GPU 会同步以互相提供它们缺少的部分。
为了说明这个想法,考虑一个具有 3 层(La,Lb 和 Lc)的简单模型,其中每层有 3 个参数。例如,层 La 有权重 a0,a1 和 a2:
La | Lb | Lc
---|----|---
a0 | b0 | c0
a1 | b1 | c1
a2 | b2 | c2
如果我们有 3 个 GPU,ZeRO-DP 将模型分割到 3 个 GPU 上:
GPU0:
La | Lb | Lc
---|----|---
a0 | b0 | c0
GPU1:
La | Lb | Lc
---|----|---
a1 | b1 | c1
GPU2:
La | Lb | Lc
---|----|---
a2 | b2 | c2
在某种程度上,这与张量并行性相同的水平切片,与垂直切片相对,其中将整个层组放在不同的 GPU 上。现在让我们看看这是如何工作的:
这些 GPU 中的每一个都将像 DP 中那样获得通常的小批量:
x0 => GPU0
x1 => GPU1
x2 => GPU2
输入被传递而不进行修改,就好像原始模型会处理它们一样。
首先,输入到达层La
。在这一点上会发生什么?
在 GPU0 上:x0 小批量需要 a0,a1,a2 参数通过层进行前向路径,但 GPU0 只有 a0。它将从 GPU1 获取 a1,从 GPU2 获取 a2,将模型的所有部分汇集在一起。
同时,GPU1 获得另一个小批量-x1。GPU1 具有 a1 参数,但需要 a0 和 a2,因此它从 GPU0 和 GPU2 获取这些。GPU2 也发生同样的情况,它获得小批量 x2。它从 GPU0 和 GPU1 获取 a0 和 a1。
这样,每个 3 个 GPU 都会得到完整的张量重建,并使用自己的小批量进行前向传递。一旦计算完成,不再需要的数据将被丢弃-它只在计算过程中使用。重建通过预取高效地完成。
然后整个过程会为层 Lb 重复,然后是 Lc 的前向,然后是 Lc -> Lb -> La 的后向。
这种机制类似于一种高效的团体背包策略:A 人携带帐篷,B 人携带炉灶,C 人携带斧头。每晚他们都分享自己拥有的东西,并从他人那里得到他们没有的东西,早上他们收拾好自己分配的装备类型,继续前进。这就是 ZeRO DP/Sharded DDP。将这种策略与每个人都必须携带自己的帐篷、炉灶和斧头的简单策略进行比较(类似于 PyTorch 中的 DataParallel(DP 和 DDP)),后者将更加低效。
在阅读这个主题的文献时,您可能会遇到以下同义词:分片,分区。如果您仔细注意 ZeRO 如何分割模型的权重-它看起来非常类似于张量并行性,稍后将对此进行讨论。这是因为它分割/分片每个层的权重,与接下来将讨论的垂直模型并行性不同。
实施:
从天真的模型并行性到管道并行性
解释管道并行性,我们首先将研究天真的模型并行性(MP),也称为垂直 MP。这种方法涉及通过使用.to()
将模型层组分配到多个 GPU 上,将特定层分配给特定 GPU。当数据流经这些层时,它被移动到与该层相同的 GPU 上,而其他层保持不变。
我们将这种模型并行性称为“垂直”,因为模型通常是如何可视化的。例如,以下图表显示将 8 层模型垂直分割成两个切片,将层 0-3 放在 GPU0 上,将层 4-7 放在 GPU1 上:
=================== ===================
| 0 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 |
=================== ===================
GPU0 GPU1
在这个例子中,当数据从第 0 层移动到第 3 层时,与常规前向传递没有区别。然而,将数据从第 3 层传递到第 4 层需要将其从 GPU0 移动到 GPU1,引入了通信开销。如果参与的 GPU 在同一计算节点上(例如同一台物理机器),这种复制是快速的,但如果 GPU 分布在不同的计算节点上(例如多台机器),通信开销可能会大大增加。
接下来,第 4 到第 7 层的工作方式与原始模型中的工作方式相同。在完成第 7 层后,通常需要将数据发送回第 0 层,那里有标签(或者将标签发送到最后一层)。现在可以计算损失并让优化器开始工作。
天真的模型并行存在一些缺点:
-
除了一个 GPU 外,任何时刻都有空闲的 GPU:如果使用 4 个 GPU,几乎等同于将单个 GPU 的内存量增加四倍,并忽略其他硬件。
-
设备之间数据传输的开销:例如,使用天真的 MP,4 张 6GB 的卡片可以容纳与 1 张 24GB 卡片相同大小的模型,但是单张 24GB 卡片会更快地完成训练,因为它没有数据复制的开销。但是,假设您有 40GB 的卡片,需要适应一个 45GB 的模型,您可以使用 4 张 40GB 的卡片(但是可能很勉强,因为梯度和优化器状态)。
-
复制共享的嵌入:共享的嵌入可能需要在 GPU 之间来回复制。
现在您已经了解了模型并行的天真方法以及其缺点,让我们来看看管道并行(PP)。PP 几乎与天真的 MP 相同,但通过将传入的批次分成微批次并人为地创建一个管道来解决 GPU 空闲问题,这允许不同的 GPU 同时参与计算过程。
以下来自GPipe 论文的插图显示了顶部的天真 MP 和底部的 PP:
在图表底部,您可以观察到管道并行(PP)方法最小化了空闲 GPU 区域的数量,称为“气泡”。图表的两部分显示了并行度为 4 的水平,意味着有 4 个 GPU 参与了管道。您可以看到有一个由 4 个管道阶段(F0、F1、F2 和 F3)组成的前向路径,然后是一个反向路径,顺序相反(B3、B2、B1 和 B0)。
PP 引入了一个新的超参数来调整 - chunks
,它确定通过同一管道阶段连续发送多少数据块。例如,在底部图表中,您可以看到chunks=4
。GPU0 在块 0、1、2 和 3(F0,0、F0,1、F0,2、F0,3)上执行相同的前向路径,然后等待其他 GPU 完成它们的工作。只有当其他 GPU 开始完成它们的工作时,GPU0 才开始再次工作,为块 3、2、1 和 0(B0,3、B0,2、B0,1、B0,0)执行反向路径。
请注意,这与梯度累积步骤的概念相同。PyTorch 使用chunks
,而 DeepSpeed 将同一超参数称为梯度累积步骤。
由于 chunks,PP 引入了微批次(MBS)的概念。DP 将全局数据批次大小分成小批次,因此如果 DP 度为 4,则全局批次大小为 1024 将分成 4 个每个 256 的小批次(1024/4)。如果chunks
(或 GAS)的数量为 32,则我们最终得到一个微批次大小为 8(256/32)。每个管道阶段一次处理一个微批次。要计算 DP + PP 设置的全局批次大小,请使用公式:mbs * chunks * dp_degree
(8 * 32 * 4 = 1024
)。当chunks=1
时,您将得到天真的 MP,这是低效的。当chunks
值很大时,您将得到微小的微批次大小,这也是低效的。因此,我们鼓励尝试不同的chunks
值,以找到导致最有效的 GPU 利用率的值。
您可能会注意到图表上的“死”时间泡泡,因为最后的forward
阶段必须等待backward
完成管道。找到chunks
的最佳值的目的是实现所有参与 GPU 之间的高并发 GPU 利用率,从而最小化泡泡的大小。
Pipeline API 解决方案已在以下平台中实施:
-
PyTorch
-
DeepSpeed
-
Megatron-LM
这些存在一些缺点:
-
他们必须对模型进行相当大的修改,因为 Pipeline 要求将模块的正常流重写为相同的
nn.Sequential
序列,这可能需要对模型的设计进行更改。 -
目前,Pipeline API 非常受限制。如果在管道的第一个阶段传递了一堆 Python 变量,您将不得不找到解决方法。目前,管道接口要求作为唯一输入和输出的是单个张量或张量元组。这些张量的批次大小必须作为第一个维度,因为管道将小批次分成微批次。这里正在讨论可能的改进
github.com/pytorch/pytorch/pull/50693
-
在管道阶段的条件控制流不可能-例如,编码器-解码器模型(如 T5)需要特殊的解决方案来处理条件编码器阶段。
-
他们必须安排每一层,以便一层的输出成为另一层的输入。
更近期的解决方案包括:
-
Varuna
-
Sagemaker
我们尚未尝试 Varuna 和 SageMaker,但他们的论文报告称,他们已经克服了上述问题列表,并且需要对用户模型进行较小的更改。
实施:
-
PyTorch(在 pytorch-1.8 中提供初始支持,并在 1.9 中逐渐改进,在 1.10 中更是如此)。一些示例
-
Megatron-LM具有内部实现-没有 API。
-
SageMaker - 这是一种专有解决方案,只能在 AWS 上使用。
-
OSLO - 这是基于 Hugging Face Transformers 实现的。
🤗 Transformers 状态:截至目前,没有模型支持完整的 PP。GPT2 和 T5 模型具有天真的 MP 支持。主要障碍是无法将模型转换为nn.Sequential
并使所有输入为张量。这是因为当前模型包含许多使转换非常复杂的特性,并且需要将其删除才能实现转换。
DeepSpeed 和 Megatron-LM 集成可在🤗 Accelerate中找到
其他方法:
DeepSpeed、Varuna 和 SageMaker 使用交错管道的概念
在这里,通过优先考虑反向传递来进一步减少气泡(空闲时间)。Varuna 通过使用模拟来发现最有效的调度来尝试改进时间表。
OSLO 具有基于 Transformers 的管道并行实现,无需nn.Sequential
转换。
张量并行
在张量并行中,每个 GPU 处理张量的一个切片,并且仅在需要时聚合完整的张量进行操作。为了描述这种方法,本指南的这一部分依赖于Megatron-LM论文中的概念和图表:在 GPU 集群上进行高效的大规模语言模型训练。
任何 transformer 的主要构建块是一个完全连接的nn.Linear
,后面跟着一个非线性激活GeLU
。它的点积部分,根据 Megatron 的论文符号表示法,可以写成Y = GeLU(XA)
,其中X
是输入向量,Y
是输出向量,A
是权重矩阵。
如果我们以矩阵形式看计算,您可以看到矩阵乘法如何在多个 GPU 之间分割:
如果我们将权重矩阵A
在N
个 GPU 上按列划分,并在并行执行矩阵乘法XA_1
到XA_n
,那么我们将得到N
个输出向量Y_1, Y_2, ..., Y_n
,可以独立地输入到GeLU
中:
使用这个原则,我们可以更新任意深度的多层感知器,而无需在 GPU 之间进行任何同步,直到最后,在那里我们需要从碎片中重建输出向量。Megatron-LM 论文的作者为此提供了一个有用的插图:
并行化多头注意力层甚至更简单,因为它们已经天生是并行的,由于具有多个独立的头!
特殊考虑:TP 需要非常快的网络,因此不建议在多个节点之间进行 TP。实际上,如果一个节点有 4 个 GPU,则最高的 TP 度数为 4。如果您需要 8 的 TP 度数,则需要使用至少有 8 个 GPU 的节点。
替代名称:
- DeepSpeed 称之为张量切片
实施:
-
Megatron-LM具有内部实现,因为它非常特定于模型。
-
parallelformers(目前仅推理)
-
SageMaker - 这是一个专有解决方案,只能在 AWS 上使用。
-
OSLO具有基于 Transformers 的张量并行实现。
SageMaker 将 TP 与 DP 结合起来,以实现更高效的处理。
🤗 Transformers 状态:
-
核心:核心尚未实现
-
但是如果您需要推理parallelformers为我们大多数模型提供了支持。因此,在核心实现之前,您可以使用它们的支持。希望训练模式也会得到支持。
-
Deepspeed-Inference 还支持我们的 BERT、GPT-2 和 GPT-Neo 模型,采用超快的基于 CUDA 内核的推理模式,更多信息请参见这里
🤗 Accelerate 集成了Megatron-LM 的 TP。
数据并行 + 管道并行
来自 DeepSpeed pipeline 教程的以下图表演示了如何将 DP 与 PP 结合使用。
在这里,重要的是看到 DP 等级 0 看不到 GPU2,DP 等级 1 看不到 GPU3。对于 DP 来说,只有 GPU 0 和 1,它们像只有 2 个 GPU 一样提供数据。GPU0“秘密”地将一些负载转移到 GPU2 上,使用 PP。GPU1 也通过将 GPU3 列入其援助来做同样的事情。
由于每个维度至少需要 2 个 GPU,所以这里至少需要 4 个 GPU。
实现:
🤗 Transformers 状态:尚未实现
数据并行 + 流水线并行 + 张量并行
为了获得更高效的训练,使用了 3D 并行,其中 PP 与 TP 和 DP 结合使用。可以在以下图表中看到。
这个图表来自一篇博文3D 并行:扩展到万亿参数模型,也是一篇很好的阅读。
由于每个维度至少需要 2 个 GPU,所以这里至少需要 8 个 GPU。
实现:
-
DeepSpeed - DeepSpeed 还包括一个更高效的 DP,他们称之为 ZeRO-DP。
🤗 Transformers 状态:尚未实现,因为我们没有 PP 和 TP。
ZeRO 数据并行 + 流水线并行 + 张量并行
DeepSpeed 的主要特点之一是 ZeRO,它是 DP 的一个超可扩展扩展。已经在 ZeRO 数据并行中讨论过。通常它是一个独立的功能,不需要 PP 或 TP。但它可以与 PP 和 TP 结合使用。
当 ZeRO-DP 与 PP(和可选的 TP)结合时,通常只启用 ZeRO 阶段 1(优化器分片)。
虽然理论上可以使用 ZeRO 阶段 2(梯度分片)与流水线并行,但会对性能产生负面影响。每个微批次都需要额外的 reduce-scatter 集合来聚合梯度,然后再进行分片,这会增加潜在的显著通信开销。由于流水线并行的特性,使用小微批次,而重点是尝试平衡算术强度(微批次大小)和最小化流水线气泡(微批次数量)。因此,这些通信成本将影响性能。
此外,由于 PP 已经减少了梯度大小1/PP
,所以梯度分片的节省并不像纯 DP 那样显著。
ZeRO 阶段 3 也不是一个好选择,原因是需要更多的节点间通信。
由于我们有 ZeRO,另一个好处是 ZeRO-Offload。由于这是阶段 1 优化器状态可以转移到 CPU。
实现:
-
Megatron-DeepSpeed和BigScience 的 Megatron-Deepspeed,这是前一个存储库的分支。
重要论文:
- 使用 DeepSpeed 和 Megatron 训练 Megatron-Turing NLG 530B,一个大规模生成式语言模型
🤗 Transformers 状态:尚未实现,因为我们没有 PP 和 TP。
FlexFlow
FlexFlow 也以略有不同的方式解决了并行化问题。
它执行一种 4D 并行化,涵盖样本-操作-属性-参数。
-
样本 = 数据并行化(样本方向并行)
-
操作 = 将单个操作并行化为多个子操作
-
属性 = 数据并行化(长度方向并行)
-
参数 = 模型并行化(无论维度是水平还是垂直)
示例:
- 样本
让我们拿 10 批次的序列长度为 512。如果我们按样本维度将它们并行化为 2 个设备,我们得到 10 x 512,这将变为 5 x 2 x 512。
- 操作
如果我们执行层归一化,首先计算标准差,然后计算均值,然后我们可以对数据进行归一化。操作并行性允许并行计算标准差和均值。因此,如果我们按操作维度将它们并行化为 2 个设备(cuda:0,cuda:1),首先将输入数据复制到两个设备中,cuda:0 同时计算标准差,cuda:1 计算均值。
- 属性
我们有 10 批次,每个长度为 512。如果我们按属性维度将它们并行化为 2 个设备,10 x 512 将变为 10 x 2 x 256。
- 参数
这与张量模型并行化或天真的逐层模型并行化类似。
这个框架的重要性在于,它以算法方式占用资源,如(1)GPU/TPU/CPU vs.(2)RAM/DRAM vs.(3)快速内部连接/慢速外部连接,并自动优化所有这些,决定在哪里使用哪种并行化。
一个非常重要的方面是,FlexFlow 专为优化具有静态和固定工作负载的 DNN 并行化而设计,因为具有动态行为的模型可能会在迭代中更喜欢不同的并行化策略。
因此,这个承诺非常吸引人 - 它在所选集群上运行 30 分钟的模拟,并提出了最佳策略来利用这个特定环境。如果添加/删除/替换任何部分,它将运行并重新优化该计划。然后您可以进行训练。不同的设置将有自己的定制优化。
🤗 Transformers 状态:Transformers 模型可以通过transformers.utils.fx进行 FX-trace-able,这是 FlexFlow 的先决条件,但需要在 FlexFlow 方面进行更改以使其与 Transformers 模型配合使用。
GPU 选择
在多个 GPU 上训练时,您可以指定要使用的 GPU 数量和顺序。例如,当您有计算能力不同的 GPU 并希望首先使用速度更快的 GPU 时,这可能很有用。选择过程适用于DistributedDataParallel和DataParallel,以仅使用可用 GPU 的子集,您不需要 Accelerate 或 DeepSpeed integration。
GPU 的数量
例如,如果您有 4 个 GPU,但只想使用前 2 个:
torchrunAccelerateDeepSpeed
使用--nproc_per_node
来选择使用多少个 GPU。
torchrun --nproc_per_node=2 trainer-program.py ...
GPU 的顺序
现在,要选择要使用的 GPU 及其顺序,您将使用CUDA_VISIBLE_DEVICES
环境变量。最简单的方法是在~/bashrc
或其他启动配置文件中设置环境变量。CUDA_VISIBLE_DEVICES
用于映射要使用的 GPU。例如,如果您有 4 个 GPU(0、1、2、3),但只想运行 GPU 0 和 2:
CUDA_VISIBLE_DEVICES=0,2 torchrun trainer-program.py ...
只有 2 个物理 GPU(0 和 2)对 PyTorch 是“可见的”,它们分别映射到cuda:0
和cuda:1
。您还可以颠倒 GPU 的顺序以先使用 2 个。现在,映射是 GPU 0 为cuda:1
,GPU 2 为cuda:0
。
CUDA_VISIBLE_DEVICES=2,0 torchrun trainer-program.py ...
您还可以将 CUDA_VISIBLE_DEVICES
环境变量设置为空值,以创建一个没有 GPU 的环境。
CUDA_VISIBLE_DEVICES= python trainer-program.py ...
与任何环境变量一样,它们可以被导出,而不是添加到命令行中。然而,这并不推荐,因为如果您忘记了环境变量的设置方式,最终使用了错误的 GPU,会让人感到困惑。相反,通常的做法是在同一命令行上为特定的训练运行设置环境变量。
CUDA_DEVICE_ORDER
是一个替代环境变量,您可以使用它来控制 GPU 的顺序。您可以按照以下方式对它们进行排序:
- 与
nvidia-smi
和rocm-smi
分别匹配的 PCIe 总线 ID,用于 NVIDIA 和 AMD GPU
export CUDA_DEVICE_ORDER=PCI_BUS_ID
- GPU 计算能力
export CUDA_DEVICE_ORDER=FASTEST_FIRST
如果您的训练设置包括一台较旧和一台较新的 GPU,其中较旧的 GPU 显示在前,但您无法物理交换卡片使较新的 GPU 显示在前,那么 CUDA_DEVICE_ORDER
就特别有用。在这种情况下,设置 CUDA_DEVICE_ORDER=FASTEST_FIRST
,始终使用较新和更快的 GPU(nvidia-smi
或 rocm-smi
仍然按照 PCIe 顺序报告 GPU)。或者您也可以设置 export CUDA_VISIBLE_DEVICES=1,0
。
完全分片数据并行
完全分片数据并行(FSDP)是一种数据并行方法,它将模型的参数、梯度和优化器状态分片到可用 GPU(也称为工作器或rank)的数量上。与分布式数据并行(DDP)不同,FSDP 减少了内存使用,因为模型在每个 GPU 上都有副本。这提高了 GPU 内存效率,并允许您在较少的 GPU 上训练更大的模型。FSDP 与 Accelerate 集成,Accelerate 是一个用于轻松管理分布式环境中训练的库,这意味着可以从 Trainer 类中使用它。
在开始之前,请确保已安装 Accelerate,并且至少安装了 PyTorch 2.1.0 或更新版本。
pip install accelerate
FSDP 配置
首先,运行accelerate config
命令,为您的训练环境创建一个配置文件。Accelerate 使用此配置文件根据您在accelerate config
中选择的训练选项自动设置正确的训练环境。
accelerate config
当您运行accelerate config
时,您将被提示一系列选项来配置您的训练环境。本节涵盖了一些最重要的 FSDP 选项。要了解更多关于其他可用的 FSDP 选项,请查看fsdp_config参数。
分片策略
FSDP 提供了许多分片策略可供选择:
-
FULL_SHARD
- 在工作器之间对模型参数、梯度和优化器状态进行分片;选择1
作为此选项 -
SHARD_GRAD_OP
- 在工作器之间对梯度和优化器状态进行分片;选择2
作为此选项 -
NO_SHARD
- 不对任何内容进行分片(相当于 DDP);选择3
作为此选项 -
HYBRID_SHARD
- 在每个工作器内对模型参数、梯度和优化器状态进行分片,每个工作器也有完整副本;选择4
作为此选项 -
HYBRID_SHARD_ZERO2
- 在每个工作器内对梯度和优化器状态进行分片,每个工作器也有完整副本;选择5
作为此选项
这是通过fsdp_sharding_strategy
标志启用的。
CPU 卸载
当参数和梯度不在使用时,您还可以将它们卸载到 CPU 以节省更多 GPU 内存,并帮助您适应大型模型,即使 FSDP 可能不足。这可以通过在运行accelerate config
时设置fsdp_offload_params: true
来启用。
包装策略
FSDP 通过包装网络中的每一层来应用。包装通常以嵌套方式应用,其中完整权重在每次前向传递后被丢弃,以节省内存供下一层使用。自动包装策略是实现这一点的最简单方法,您无需更改任何代码。您应该选择fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
来包装一个 Transformer 层,并选择fsdp_transformer_layer_cls_to_wrap
来指定要包装的层(例如BertLayer
)。
否则,您可以选择基于大小的包装策略,如果某一层的参数超过一定数量,则应用 FSDP。这可以通过将fsdp_wrap_policy: SIZE_BASED_WRAP
和min_num_param
设置为所需的大小阈值来启用。
检查点
中间检查点应该使用fsdp_state_dict_type: SHARDED_STATE_DICT
保存,因为在 rank 0 上使用 CPU 卸载保存完整状态字典需要很长时间,并且经常由于广播期间的无限挂起而导致NCCL Timeout
错误。您可以使用load_state`方法恢复训练。
# directory containing checkpoints
accelerator.load_state("ckpt")
然而,当训练结束时,您希望保存完整状态字典,因为分片状态字典仅与 FSDP 兼容。
if trainer.is_fsdp_enabled:
trainer.accelerator.state.fsdp_plugin.set_state_dict_type("FULL_STATE_DICT")
trainer.save_model(script_args.output_dir)
TPU
PyTorch XLA支持 TPU 的 FSDP 训练,可以通过修改accelerate config
生成的 FSDP 配置文件来启用。除了上面指定的分片策略和包装选项之外,您还可以向文件中添加下面显示的参数。
xla: True # must be set to True to enable PyTorch/XLA
xla_fsdp_settings: # XLA-specific FSDP parameters
xla_fsdp_grad_ckpt: True # use gradient checkpointing
xla_fsdp_settings
允许您配置 FSDP 的额外 XLA 特定参数。
启动训练
一个示例的 FSDP 配置文件可能如下所示:
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: FSDP
downcast_bf16: 'no'
fsdp_config:
fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
fsdp_backward_prefetch_policy: BACKWARD_PRE
fsdp_cpu_ram_efficient_loading: true
fsdp_forward_prefetch: false
fsdp_offload_params: true
fsdp_sharding_strategy: 1
fsdp_state_dict_type: SHARDED_STATE_DICT
fsdp_sync_module_states: true
fsdp_transformer_layer_cls_to_wrap: BertLayer
fsdp_use_orig_params: true
machine_rank: 0
main_training_function: main
mixed_precision: bf16
num_machines: 1
num_processes: 2
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
启动训练,请运行accelerate launch
命令,它将自动使用您之前使用accelerate config
创建的配置文件。
accelerate launch my-trainer-script.py
accelerate launch --fsdp="full shard" --fsdp_config="path/to/fsdp_config/ my-trainer-script.py
下一步
FSDP 可以是训练非常大模型的强大工具,如果您有多个 GPU 或 TPU 可用。通过对模型参数、优化器和梯度状态进行分片,甚至在它们不活动时将它们卸载到 CPU 上,FSDP 可以减少大规模训练的高成本。如果您有兴趣了解更多,以下内容可能有所帮助:
-
按照更详细的FSDP加速指南进行操作。
在 CPU 上高效训练
原文:
huggingface.co/docs/transformers/v4.37.2/en/perf_train_cpu
本指南侧重于在 CPU 上高效训练大型模型。
使用 IPEX 的混合精度
IPEX 优化了 AVX-512 或更高版本的 CPU,并且在仅具有 AVX2 的 CPU 上也可以正常工作。因此,预计在具有 AVX-512 或更高版本的英特尔 CPU 世代中,IPEX 将带来性能优势,而仅具有 AVX2 的 CPU(例如 AMD CPU 或较旧的英特尔 CPU)在 IPEX 下可能会获得更好的性能,但不能保证。IPEX 为使用 Float32 和 BFloat16 进行 CPU 训练提供了性能优化。以下部分的主要重点是使用 BFloat16。
低精度数据类型 BFloat16 已经在第三代 Xeon® Scalable Processors(又称 Cooper Lake)上本地支持,具有 AVX512 指令集,并将在下一代英特尔® Xeon® Scalable Processors 上支持,该处理器具有 Intel® Advanced Matrix Extensions(Intel® AMX)指令集,进一步提升性能。自 PyTorch-1.10 以来,已启用了 CPU 后端的自动混合精度。同时,Intel® Extension for PyTorch 大规模启用了 CPU 和 BFloat16 优化运算符的自动混合精度,并部分上游到 PyTorch 主分支。用户可以通过 IPEX 自动混合精度获得更好的性能和用户体验。
查看更多关于 自动混合精度 的详细信息。
IPEX 安装:
IPEX 发布遵循 PyTorch,可以通过 pip 安装:
PyTorch 版本 | IPEX 版本 |
---|---|
2.1.x | 2.1.100+cpu |
2.0.x | 2.0.100+cpu |
1.13 | 1.13.0+cpu |
1.12 | 1.12.300+cpu |
请运行 pip list | grep torch
以获取您的 pytorch_version
,这样您就可以获得 IPEX version_name
。
pip install intel_extension_for_pytorch==<version_name> -f https://developer.intel.com/ipex-whl-stable-cpu
如果需要,您可以在 ipex-whl-stable-cpu 中查看最新版本。
查看更多关于 IPEX 安装 的方法。
Trainer 中的用法
要在 Trainer 中启用 IPEX 的自动混合精度,用户应该在训练命令参数中添加 use_ipex
、bf16
和 no_cuda
。
以 Transformers 问答 用例为例
-
使用 BF16 自动混合精度在 CPU 上进行 IPEX 训练:
python run_qa.py \ --model_name_or_path bert-base-uncased \ --dataset_name squad \ --do_train \ --do_eval \ --per_device_train_batch_size 12 \ --learning_rate 3e-5 \ --num_train_epochs 2 \ --max_seq_length 384 \ --doc_stride 128 \ --output_dir /tmp/debug_squad/ \ --use_ipex \ --bf16 \ --use_cpu
如果要在脚本中启用 use_ipex
和 bf16
,请像这样将这些参数添加到 TrainingArguments
中:
training_args = TrainingArguments(
output_dir=args.output_path,
+ bf16=True,
+ use_ipex=True,
+ use_cpu=True,
**kwargs
)
实践示例
博客:使用英特尔 Sapphire Rapids 加速 PyTorch Transformers
多 CPU 高效训练
原始文本:
huggingface.co/docs/transformers/v4.37.2/en/perf_train_cpu_many
当在单个 CPU 上训练速度太慢时,我们可以使用多个 CPU。本指南侧重于基于 PyTorch 的 DDP,可以在 bare metal 和 Kubernetes 上有效地启用分布式 CPU 训练。
Intel® oneCCL 绑定的 PyTorch
Intel® oneCCL(集体通信库)是一个用于实现 allreduce、allgather、alltoall 等集体通信的高效分布式深度学习训练的库。有关 oneCCL 的更多信息,请参考oneCCL 文档和oneCCL 规范。
模块oneccl_bindings_for_pytorch
(在 1.12 版本之前为torch_ccl
)实现了 PyTorch C10D ProcessGroup API,并且可以作为外部 ProcessGroup 动态加载,目前仅在 Linux 平台上可用。
查看更多关于oneccl_bind_pt的详细信息。
Intel® oneCCL 绑定的 PyTorch 安装
以下 Python 版本的 Wheel 文件可用:
扩展版本 | Python 3.6 | Python 3.7 | Python 3.8 | Python 3.9 | Python 3.10 |
---|---|---|---|---|---|
2.1.0 | √ | √ | √ | √ | |
2.0.0 | √ | √ | √ | √ | |
1.13.0 | √ | √ | √ | √ | |
1.12.100 | √ | √ | √ | √ | |
1.12.0 | √ | √ | √ | √ |
请运行pip list | grep torch
以获取您的pytorch_version
。
pip install oneccl_bind_pt=={pytorch_version} -f https://developer.intel.com/ipex-whl-stable-cpu
其中{pytorch_version}
应该是您的 PyTorch 版本,例如 2.1.0。查看更多关于oneccl_bind_pt 安装的方法。oneCCL 和 PyTorch 的版本必须匹配。
oneccl_bindings_for_pytorch 1.12.0 预构建的 Wheel 文件与 PyTorch 1.12.1 不兼容(适用于 PyTorch 1.12.0)。PyTorch 1.12.1 应该与 oneccl_bindings_for_pytorch 1.12.100 兼容。
Intel® MPI 库
使用这个基于标准的 MPI 实现来在 Intel®架构上提供灵活、高效、可扩展的集群消息传递。这个组件是 Intel® oneAPI HPC Toolkit 的一部分。
oneccl_bindings_for_pytorch 是与 MPI 工具集一起安装的。在使用之前需要设置环境。
对于 Intel® oneCCL >= 1.12.0
oneccl_bindings_for_pytorch_path=$(python -c "from oneccl_bindings_for_pytorch import cwd; print(cwd)")
source $oneccl_bindings_for_pytorch_path/env/setvars.sh
对于版本< 1.12.0 的 Intel® oneCCL
torch_ccl_path=$(python -c "import torch; import torch_ccl; import os; print(os.path.abspath(os.path.dirname(torch_ccl.__file__)))")
source $torch_ccl_path/env/setvars.sh
Intel® PyTorch 扩展安装
Intel PyTorch 扩展(IPEX)为使用 Float32 和 BFloat16 进行 CPU 训练提供了性能优化(请参考单 CPU 部分以了解更多信息)。
以下“Trainer 中的用法”以 Intel® MPI 库中的 mpirun 为例。
在 Trainer 中的用法
要在 Trainer 中使用 ccl 后端启用多 CPU 分布式训练,用户应在命令参数中添加--ddp_backend ccl
。
让我们看一个例子,使用问答示例
以下命令在一个 Xeon 节点上启用了使用 2 个进程进行训练,每个进程在一个插槽上运行。OMP_NUM_THREADS/CCL_WORKER_COUNT 变量可以调整以获得最佳性能。
export CCL_WORKER_COUNT=1
export MASTER_ADDR=127.0.0.1
mpirun -n 2 -genv OMP_NUM_THREADS=23 \
python3 run_qa.py \
--model_name_or_path bert-large-uncased \
--dataset_name squad \
--do_train \
--do_eval \
--per_device_train_batch_size 12 \
--learning_rate 3e-5 \
--num_train_epochs 2 \
--max_seq_length 384 \
--doc_stride 128 \
--output_dir /tmp/debug_squad/ \
--no_cuda \
--ddp_backend ccl \
--use_ipex
以下命令在两个 Xeon 上(node0 和 node1,以 node0 为主进程)启用了总共四个进程的训练,ppn(每个节点的进程数)设置为 2,每个插槽上运行一个进程。OMP_NUM_THREADS/CCL_WORKER_COUNT 变量可以调整以获得最佳性能。
在 node0 中,您需要创建一个包含每个节点的 IP 地址的配置文件(例如 hostfile),并将该配置文件路径作为参数传递。
cat hostfile
xxx.xxx.xxx.xxx #node0 ip
xxx.xxx.xxx.xxx #node1 ip
现在,在 node0 中运行以下命令,将在 node0 和 node1 中启用 4DDP,并使用 BF16 自动混合精度:
export CCL_WORKER_COUNT=1
export MASTER_ADDR=xxx.xxx.xxx.xxx #node0 ip
mpirun -f hostfile -n 4 -ppn 2 \
-genv OMP_NUM_THREADS=23 \
python3 run_qa.py \
--model_name_or_path bert-large-uncased \
--dataset_name squad \
--do_train \
--do_eval \
--per_device_train_batch_size 12 \
--learning_rate 3e-5 \
--num_train_epochs 2 \
--max_seq_length 384 \
--doc_stride 128 \
--output_dir /tmp/debug_squad/ \
--no_cuda \
--ddp_backend ccl \
--use_ipex \
--bf16
在 Kubernetes 中的用法
可以使用Kubeflow PyTorchJob 训练操作符将前一节中的相同分布式训练作业部署到 Kubernetes 集群。
设置
此示例假定您已经:
-
已安装并配置
kubectl
以访问 Kubernetes 集群 -
可以用于存储数据集和模型文件的Persistent Volume Claim (PVC)。设置 PVC 的多种选项,包括使用 NFS storage class或云存储桶。
-
一个包含您的模型训练脚本和运行脚本所需的所有依赖项的 Docker 容器。对于分布式 CPU 训练作业,这通常包括 PyTorch、Transformers、Intel Extension for PyTorch、Intel oneCCL Bindings for PyTorch 和 OpenSSH 以在容器之间进行通信。
下面的片段是一个使用支持分布式 CPU 训练的基础镜像的 Dockerfile 示例,然后将 Transformers 发布提取到/workspace
目录中,以便示例脚本包含在镜像中:
FROM intel/ai-workflows:torch-2.0.1-huggingface-multinode-py3.9
WORKDIR /workspace
# Download and extract the transformers code
ARG HF_TRANSFORMERS_VER="4.35.2"
RUN mkdir transformers && \
curl -sSL --retry 5 https://github.com/huggingface/transformers/archive/refs/tags/v${HF_TRANSFORMERS_VER}.tar.gz | tar -C transformers --strip-components=1 -xzf -
在将 PyTorchJob 部署到集群之前,需要构建并将镜像复制到集群的节点或推送到容器注册表。
PyTorchJob 规范文件
Kubeflow PyTorchJob用于在集群上运行分布式训练作业。PyTorchJob 的 yaml 文件定义了参数,例如:
-
PyTorchJob 的名称
-
副本数(workers)的数量
-
将用于运行训练作业的 Python 脚本及其参数
-
每个 worker 所需的资源类型(节点选择器、内存和 CPU)
-
Docker 容器使用的图像/标签
-
环境变量
-
PVC 的卷挂载
卷挂载定义了 PVC 将在每个 worker pod 的容器中挂载的路径。此位置可用于数据集、检查点文件以及训练完成后保存的模型。
下面的片段是一个 PyTorchJob 的 yaml 文件示例,其中有 4 个 worker 运行问答示例。
apiVersion: "kubeflow.org/v1"
kind: PyTorchJob
metadata:
name: transformers-pytorchjob
namespace: kubeflow
spec:
elasticPolicy:
rdzvBackend: c10d
minReplicas: 1
maxReplicas: 4
maxRestarts: 10
pytorchReplicaSpecs:
Worker:
replicas: 4 # The number of worker pods
restartPolicy: OnFailure
template:
spec:
containers:
- name: pytorch
image: <image name>:<tag> # Specify the docker image to use for the worker pods
imagePullPolicy: IfNotPresent
command:
- torchrun
- /workspace/transformers/examples/pytorch/question-answering/run_qa.py
- --model_name_or_path
- "bert-large-uncased"
- --dataset_name
- "squad"
- --do_train
- --do_eval
- --per_device_train_batch_size
- "12"
- --learning_rate
- "3e-5"
- --num_train_epochs
- "2"
- --max_seq_length
- "384"
- --doc_stride
- "128"
- --output_dir
- "/tmp/pvc-mount/output"
- --no_cuda
- --ddp_backend
- "ccl"
- --use_ipex
- --bf16 # Specify --bf16 if your hardware supports bfloat16
env:
- name: LD_PRELOAD
value: "/usr/lib/x86_64-linux-gnu/libtcmalloc.so.4.5.9:/usr/local/lib/libiomp5.so"
- name: TRANSFORMERS_CACHE
value: "/tmp/pvc-mount/transformers_cache"
- name: HF_DATASETS_CACHE
value: "/tmp/pvc-mount/hf_datasets_cache"
- name: LOGLEVEL
value: "INFO"
- name: CCL_WORKER_COUNT
value: "1"
- name: OMP_NUM_THREADS # Can be tuned for optimal performance
- value: "56"
resources:
limits:
cpu: 200 # Update the CPU and memory limit values based on your nodes
memory: 128Gi
requests:
cpu: 200 # Update the CPU and memory request values based on your nodes
memory: 128Gi
volumeMounts:
- name: pvc-volume
mountPath: /tmp/pvc-mount
- mountPath: /dev/shm
name: dshm
restartPolicy: Never
nodeSelector: # Optionally use the node selector to specify what types of nodes to use for the workers
node-type: spr
volumes:
- name: pvc-volume
persistentVolumeClaim:
claimName: transformers-pvc
- name: dshm
emptyDir:
medium: Memory
要运行此示例,请根据您的训练脚本和集群中的节点更新 yaml。
yaml 中的 CPU 资源限制/请求是以CPU 单位定义的,其中 1 个 CPU 单位等同于 1 个物理 CPU 核心或 1 个虚拟核心(取决于节点是物理主机还是虚拟机)。在 yaml 中定义的 CPU 和内存限制/请求量应小于单台机器上可用 CPU/内存容量的量。通常最好不要使用整个机器的容量,以便为 kubelet 和操作系统留下一些资源。为了为 worker pods 获得“guaranteed”服务质量,请为资源限制和请求设置相同的 CPU 和内存量。
部署
在为您的集群和训练作业更新了适当的值后,可以使用以下命令将 PyTorchJob 部署到集群中:
kubectl create -f pytorchjob.yaml
然后可以使用kubectl get pods -n kubeflow
命令来列出kubeflow
命名空间中的 pod。您应该看到刚刚部署的 PyTorchJob 的 worker pods。起初,它们可能会显示“Pending”状态,因为容器正在被拉取和创建,然后状态应该会变为“Running”。
NAME READY STATUS RESTARTS AGE
...
transformers-pytorchjob-worker-0 1/1 Running 0 7m37s
transformers-pytorchjob-worker-1 1/1 Running 0 7m37s
transformers-pytorchjob-worker-2 1/1 Running 0 7m37s
transformers-pytorchjob-worker-3 1/1 Running 0 7m37s
...
可以使用 kubectl logs -n kubeflow <pod name>
查看工作节点的日志。添加 -f
来实时查看日志,例如:
kubectl logs -n kubeflow transformers-pytorchjob-worker-0 -f
训练作业完成后,训练好的模型可以从 PVC 或存储位置复制。当作业完成后,可以使用 kubectl delete -f pytorchjob.yaml
命令从集群中删除 PyTorchJob 资源。
摘要
本指南涵盖了在裸金属和 Kubernetes 集群上使用多个 CPU 运行分布式 PyTorch 训练作业。这两种情况都利用了 Intel Extension for PyTorch 和 Intel oneCCL Bindings for PyTorch 来实现最佳的训练性能,并可以作为在多个节点上运行自己工作负载的模板。
使用 TensorFlow 在 TPU 上训练
原始文本:
huggingface.co/docs/transformers/v4.37.2/en/perf_train_tpu_tf
如果您不需要长篇解释,只想要 TPU 代码示例来开始使用,请查看我们的 TPU 示例笔记本!
什么是 TPU?
TPU 是张量处理单元。它们是由 Google 设计的硬件,用于大大加速神经网络中的张量计算,类似于 GPU。它们可用于网络训练和推断。通常通过 Google 的云服务访问,但也可以通过 Google Colab 和 Kaggle Kernels 直接免费访问小型 TPU。
因为🤗 Transformers 中的所有 TensorFlow 模型都是 Keras 模型,因此本文档中的大多数方法通常适用于任何 Keras 模型的 TPU 训练!但是,有一些点是特定于 HuggingFace 生态系统(hug-o-system?)的 Transformers 和 Datasets,当我们到达这些点时,我们将确保标记它们。
有哪些类型的 TPU 可用?
新用户经常对各种 TPU 和访问方式感到困惑。要理解的第一个关键区别是TPU 节点和TPU VM之间的区别。
当您使用TPU 节点时,实际上是间接访问远程 TPU。您将需要一个单独的 VM,该 VM 将初始化您的网络和数据管道,然后将它们转发到远程节点。当您在 Google Colab 上使用 TPU 时,您是以TPU 节点样式访问它。
对于不习惯使用 TPU 的人来说,使用 TPU 节点可能会产生一些意想不到的行为!特别是,因为 TPU 位于与运行 Python 代码的机器物理上不同的系统上,您的数据不能是本地的 - 从您机器的内部存储加载的任何数据管道将完全失败!相反,数据必须存储在 Google Cloud Storage 中,您的数据管道仍然可以访问它,即使管道在远程 TPU 节点上运行。
如果您可以将所有数据存储在内存中作为np.ndarray
或tf.Tensor
,那么即使在使用 Colab 或 TPU 节点时,也可以在该数据上进行fit()
,而无需将其上传到 Google Cloud Storage。
🤗具体的 Hugging Face 提示🤗:Dataset.to_tf_dataset()
方法及其更高级别的包装器model.prepare_tf_dataset()
,您将在我们的 TF 代码示例中看到,都会在 TPU 节点上失败。原因是即使它们创建了一个tf.data.Dataset
,它也不是“纯粹”的tf.data
管道,并且使用tf.numpy_function
或Dataset.from_generator()
从底层 HuggingFaceDataset
中流式传输数据。这个 HuggingFaceDataset
由存储在本地磁盘上的数据支持,远程 TPU 节点将无法读取。
第二种访问 TPU 的方式是通过TPU VM。在使用 TPU VM 时,您直接连接到 TPU 连接的机器,就像在 GPU VM 上进行训练一样。TPU VM 通常更容易使用,特别是在处理数据管道时。所有上述警告不适用于 TPU VM!
这是一份主观的文件,所以这是我们的意见:尽量避免使用 TPU Node。它比 TPU VM 更令人困惑,更难以调试。未来也可能不受支持 - 谷歌最新的 TPU,TPUv4,只能作为 TPU VM 访问,这表明 TPU Node 越来越可能成为“传统”访问方法。但是,我们了解到唯一免费的 TPU 访问是在 Colab 和 Kaggle Kernels 上,它们使用 TPU Node - 因此,如果必须使用,我们将尝试解释如何处理!查看TPU 示例笔记本以获取更详细的代码示例。
可用的 TPU 尺寸是多少?
单个 TPU(v2-8/v3-8/v4-8)运行 8 个副本。TPU 存在于可以同时运行数百或数千个副本的pod中。当您使用多个 TPU 但少于整个 pod 时(例如 v3-32),您的 TPU 群被称为pod slice。
当您通过 Colab 访问免费的 TPU 时,通常会获得一个 v2-8 TPU。
我一直听说这个 XLA。XLA 是什么,它与 TPU 有什么关系?
XLA 是一个优化编译器,被 TensorFlow 和 JAX 同时使用。在 JAX 中,它是唯一的编译器,而在 TensorFlow 中是可选的(但在 TPU 上是强制的!)。在训练 Keras 模型时启用它的最简单方法是将参数jit_compile=True
传递给model.compile()
。如果没有出现任何错误且性能良好,那么这是一个很好的迹象,表明您已准备好转移到 TPU!
在 TPU 上进行调试通常比在 CPU/GPU 上更困难,因此我们建议在尝试在 TPU 上运行之前,先在 CPU/GPU 上使用 XLA 使您的代码能够运行。当然,您不必训练很长时间 - 只需进行几个步骤,以确保您的模型和数据流水线按照您的预期工作。
XLA 编译的代码通常更快 - 因此,即使您不打算在 TPU 上运行,添加jit_compile=True
也可以提高性能。但是,请注意下面关于 XLA 兼容性的注意事项!
基于痛苦经验的提示:虽然使用jit_compile=True
是获得速度提升并测试您的 CPU/GPU 代码是否与 XLA 兼容的好方法,但如果在实际在 TPU 上训练时保留它,可能会导致许多问题。XLA 编译将在 TPU 上隐式发生,因此在实际在 TPU 上运行代码之前,请记得删除那行!
如何使我的模型与 XLA 兼容?
在许多情况下,您的代码可能已经与 XLA 兼容!但是,有一些在普通 TensorFlow 中有效但在 XLA 中无效的事情。我们将它们概括为以下三条核心规则:
🤗具体的 HuggingFace 提示🤗:我们已经付出了很多努力,将我们的 TensorFlow 模型和损失函数重写为 XLA 兼容。我们的模型和损失函数通常默认遵守规则#1 和#2,因此如果您使用transformers
模型,则可以跳过它们。但是,在编写自己的模型和损失函数时,请不要忘记这些规则!
XLA 规则#1:您的代码不能具有“数据相关条件”
这意味着任何if
语句都不能依赖于tf.Tensor
内部的值。例如,此代码块无法使用 XLA 编译!
if tf.reduce_sum(tensor) > 10:
tensor = tensor / 2.0
这一开始可能看起来非常受限制,但大多数神经网络代码不需要这样做。您通常可以通过使用tf.cond
(请参阅此处的文档)或通过删除条件并找到一个巧妙的数学技巧来绕过此限制,例如:
sum_over_10 = tf.cast(tf.reduce_sum(tensor) > 10, tf.float32)
tensor = tensor / (1.0 + sum_over_10)
这段代码与上面的代码具有完全相同的效果,但通过避免条件语句,我们确保它将在 XLA 中编译而无问题!
XLA 规则#2:您的代码不能具有“数据相关形状”
这意味着代码中所有的tf.Tensor
对象的形状不能依赖于它们的值。例如,函数tf.unique
不能与 XLA 一起编译,因为它返回一个包含输入中每个唯一值的tensor
。这个输出的形状显然会根据输入Tensor
的重复程度而不同,因此 XLA 拒绝处理它!
一般来说,大多数神经网络代码默认遵守规则#2。但是,在一些常见情况下,这可能会成为一个问题。一个非常常见的情况是当您使用标签屏蔽时,将标签设置为负值以指示在计算损失时应忽略这些位置。如果您查看支持标签屏蔽的 NumPy 或 PyTorch 损失函数,您经常会看到类似于使用布尔索引的代码:
label_mask = labels >= 0
masked_outputs = outputs[label_mask]
masked_labels = labels[label_mask]
loss = compute_loss(masked_outputs, masked_labels)
mean_loss = torch.mean(loss)
这段代码在 NumPy 或 PyTorch 中完全正常,但在 XLA 中会出错!为什么?因为masked_outputs
和masked_labels
的形状取决于有多少位置被屏蔽 - 这使其成为数据相关形状。然而,就像规则#1 一样,我们通常可以重写这段代码,以产生完全相同的输出,而不涉及任何数据相关形状。
label_mask = tf.cast(labels >= 0, tf.float32)
loss = compute_loss(outputs, labels)
loss = loss * label_mask # Set negative label positions to 0
mean_loss = tf.reduce_sum(loss) / tf.reduce_sum(label_mask)
在这里,我们通过为每个位置计算损失,但在计算均值时将被屏蔽的位置在分子和分母中归零,从而获得与第一个块完全相同的结果,同时保持 XLA 兼容性。请注意,我们使用与规则#1 相同的技巧 - 将tf.bool
转换为tf.float32
并将其用作指示变量。这是一个非常有用的技巧,所以如果您需要将自己的代码转换为 XLA,请记住它!
XLA 规则#3:XLA 将需要为每个不同的输入形状重新编译您的模型
这是一个重要的规则。这意味着如果您的输入形状非常不同,XLA 将不得不一遍又一遍地重新编译您的模型,这将导致巨大的性能问题。这在 NLP 模型中经常出现,因为输入文本在标记化后长度不同。在其他模态中,静态形状更常见,这个规则就不是那么大的问题了。
如何避开规则#3?关键是填充 - 如果您将所有输入填充到相同的长度,然后使用attention_mask
,您可以获得与可变形状相同的结果,但没有任何 XLA 问题。然而,过度填充也会导致严重的减速 - 如果您将所有样本填充到整个数据集中的最大长度,您可能会得到由无尽填充标记组成的批次,这将浪费大量计算和内存!
解决这个问题并没有完美的方法。但是,你可以尝试一些技巧。一个非常有用的技巧是将样本批次填充到 32 或 64 个标记的倍数。这通常只会增加少量标记的数量,但会大大减少唯一输入形状的数量,因为现在每个输入形状都必须是 32 或 64 的倍数。更少的唯一输入形状意味着更少的 XLA 编译!
🤗HuggingFace 专属提示🤗:我们的分词器和数据整理器有助于解决这个问题的方法。在调用分词器时,您可以使用padding="max_length"
或padding="longest"
来获取填充数据。我们的分词器和数据整理器还有一个pad_to_multiple_of
参数,可以减少您看到的唯一输入形状的数量!
我如何在 TPU 上实际训练我的模型?
一旦您的训练是 XLA 兼容的,并且(如果您正在使用 TPU 节点/Colab)您的数据集已经准备就绪,那么在 TPU 上运行实际上非常容易!您真正需要在代码中做的改变只是添加几行代码来初始化您的 TPU,并确保您的模型和数据集都在TPUStrategy
范围内创建。查看我们的 TPU 示例笔记本以查看实际操作!
总结
这里有很多内容,让我们用一个快速的清单来总结,当您想要准备好您的模型进行 TPU 训练时可以遵循:
-
确保您的代码遵循 XLA 的三条规则
-
在 CPU/GPU 上使用
jit_compile=True
编译您的模型,并确认您可以使用 XLA 进行训练 -
要么将数据集加载到内存中,要么使用兼容 TPU 的数据集加载方法(请参阅notebook)
-
将您的代码迁移到 Colab(加速器设置为“TPU”)或 Google Cloud 上的 TPU VM
-
添加 TPU 初始化代码(请参阅notebook)
-
创建您的
TPUStrategy
,并确保数据集加载和模型创建在strategy.scope()
内(请参阅notebook) -
当您转移到 TPU 时,不要忘记再次将
jit_compile=True
去掉! -
🙏🙏🙏🥺🥺🥺
-
调用 model.fit()
-
你做到了!
在 Apple 硅上进行 PyTorch 训练
原始文本:
huggingface.co/docs/transformers/v4.37.2/en/perf_train_special
以前,在 Mac 上训练模型仅限于 CPU。随着 PyTorch v1.12 的发布,您可以利用使用 Apple 的硅 GPU 训练模型,以获得更快的性能和训练速度。在 PyTorch 中,这是通过将 Apple 的 Metal Performance Shaders(MPS)集成为后端来实现的。MPS 后端将 PyTorch 操作实现为自定义的 Metal 着色器,并将这些模块放置在mps
设备上。
一些 PyTorch 操作尚未在 MPS 中实现,将会引发错误。为了避免这种情况,您应该设置环境变量PYTORCH_ENABLE_MPS_FALLBACK=1
来使用 CPU 内核(仍会看到UserWarning
)。
如果遇到其他错误,请在PyTorch存储库中打开问题,因为 Trainer 仅集成了 MPS 后端。
设置mps
设备后,您可以:
-
在本地训练更大的网络或批量大小
-
减少数据检索延迟,因为 GPU 的统一内存架构允许直接访问完整的内存存储
-
减少成本,因为您不需要在基于云的 GPU 上进行训练或添加额外的本地 GPU
首先确保您已安装 PyTorch。MPS 加速支持 macOS 12.3+。
pip install torch torchvision torchaudio
TrainingArguments 默认使用mps
设备,如果可用的话,这意味着您不需要显式设置设备。例如,您可以在不进行任何更改的情况下自动启用 MPS 后端运行run_glue.py脚本。
export TASK_NAME=mrpc
python examples/pytorch/text-classification/run_glue.py \
--model_name_or_path bert-base-cased \
--task_name $TASK_NAME \
- --use_mps_device \
--do_train \
--do_eval \
--max_seq_length 128 \
--per_device_train_batch_size 32 \
--learning_rate 2e-5 \
--num_train_epochs 3 \
--output_dir /tmp/$TASK_NAME/ \
--overwrite_output_dir
像gloo
和nccl
这样的分布式设置的后端不受mps
设备支持,这意味着您只能在具有 MPS 后端的单个 GPU 上进行训练。
您可以在在 Mac 上介绍加速 PyTorch 训练博客文章中了解更多关于 MPS 后端的信息。
用于训练的定制硬件
原始文本:
huggingface.co/docs/transformers/v4.37.2/en/perf_hardware
您用于运行模型训练和推理的硬件可能会对性能产生重大影响。要深入了解 GPU,请务必查看 Tim Dettmer 的优秀博客文章。
让我们看看一些关于 GPU 设置的实用建议。
GPU
当您训练更大的模型时,您基本上有三个选择:
-
更大的 GPU
-
更多的 GPU
-
更多的 CPU 和 NVMe(由 DeepSpeed-Infinity 卸载)
让我们从只有一个 GPU 的情况开始。
电源和冷却
如果您购买了昂贵的高端 GPU,请确保为其提供正确的电源和足够的冷却。
电源:
一些高端消费级 GPU 卡有 2 个甚至 3 个 PCI-E 8 针电源插座。确保您有与插座数量相同的独立 12V PCI-E 8 针电缆插入卡中。不要使用同一电缆末端的 2 个分裂(也称为猪尾电缆)。也就是说,如果 GPU 上有 2 个插座,您希望从 PSU 到卡的有 2 个 PCI-E 8 针电缆,而不是一个末端有 2 个 PCI-E 8 针连接器的电缆!否则,您将无法充分发挥卡的性能。
每个 PCI-E 8 针电源电缆需要插入 PSU 侧的 12V 电源线,可以提供高达 150W 的功率。
一些其他卡可能使用 PCI-E 12 针连接器,这些连接器可以提供高达 500-600W 的功率。
低端卡可能使用 6 针连接器,提供高达 75W 的功率。
此外,您需要具有稳定电压的高端 PSU。一些质量较低的 PSU 可能无法为卡提供所需的稳定电压以使其在峰值状态下运行。
当然,PSU 需要有足够的未使用瓦特数来为卡供电。
冷却:
当 GPU 过热时,它将开始降频,并且不会提供完整的性能,甚至在温度过高时可能会关闭。
很难确定 GPU 在负载严重时应该追求的确切最佳温度,但可能在+80C 以下是好的,但更低更好 - 也许 70-75C 是一个很好的范围。降频很可能会在 84-90C 左右开始。但除了降低性能外,长时间处于非常高的温度下可能会缩短 GPU 的寿命。
接下来让我们看看拥有多个 GPU 时最重要的一个方面:连接性。
多 GPU 连接
如果您使用多个 GPU,卡的互连方式对总训练时间有很大影响。如果 GPU 在同一物理节点上,您可以运行:
nvidia-smi topo -m
它将告诉您 GPU 是如何互连的。在具有双 GPU 且通过 NVLink 连接的机器上,您很可能会看到类似以下的内容:
GPU0 GPU1 CPU Affinity NUMA Affinity
GPU0 X NV2 0-23 N/A
GPU1 NV2 X 0-23 N/A
在没有 NVLink 的不同机器上,我们可能会看到:
GPU0 GPU1 CPU Affinity NUMA Affinity
GPU0 X PHB 0-11 N/A
GPU1 PHB X 0-11 N/A
该报告包括此图例:
X = Self
SYS = Connection traversing PCIe as well as the SMP interconnect between NUMA nodes (e.g., QPI/UPI)
NODE = Connection traversing PCIe as well as the interconnect between PCIe Host Bridges within a NUMA node
PHB = Connection traversing PCIe as well as a PCIe Host Bridge (typically the CPU)
PXB = Connection traversing multiple PCIe bridges (without traversing the PCIe Host Bridge)
PIX = Connection traversing at most a single PCIe bridge
NV# = Connection traversing a bonded set of # NVLinks
因此,第一个报告NV2
告诉我们 GPU 是通过 2 个 NVLink 互连的,而第二个报告PHB
则是典型的消费级 PCIe+Bridge 设置。
检查您的设置上有什么类型的连接性。其中一些将使卡之间的通信更快(例如 NVLink),而其他一些则更慢(例如 PHB)。
根据所使用的可扩展性解决方案的类型,连接速度可能会产生重大或轻微影响。如果 GPU 需要很少同步,如 DDP,较慢连接的影响将不那么显著。如果 GPU 需要经常互发消息,如 ZeRO-DP,则更快的连接变得非常重要以实现更快的训练。
NVlink
NVLink是由 Nvidia 开发的基于线的串行多通道近距离通信链路。
每一代新产品都提供更快的带宽,例如,这里是来自Nvidia Ampere GA102 GPU Architecture的一句话:
第三代 NVLink® GA102 GPU 利用了 NVIDIA 的第三代 NVLink 接口,其中包括四个 x4 链接,每个链接在两个 GPU 之间的每个方向提供 14.0625 GB/sec 的带宽。四个链接在每个方向提供 56.25 GB/sec 的带宽,两个 GPU 之间总共提供 112.5 GB/sec 的带宽。两个 RTX 3090 GPU 可以使用 NVLink 连接在一起进行 SLI。(请注意,不支持 3 路和 4 路 SLI 配置。)
所以在nvidia-smi topo -m
的输出中,NVX
报告中的X
值越高越好。这取决于您的 GPU 架构。
让我们比较在 wikitext 的小样本上训练 gpt2 语言模型的执行。
结果是:
NVlink | 时间 |
---|---|
Y | 101 秒 |
N | 131 秒 |
您可以看到,NVLink 完成训练速度比快约 23%。在第二个基准测试中,我们使用NCCL_P2P_DISABLE=1
告诉 GPU 不要使用 NVLink。
以下是完整的基准测试代码和输出:
# DDP w/ NVLink
rm -r /tmp/test-clm; CUDA_VISIBLE_DEVICES=0,1 torchrun \
--nproc_per_node 2 examples/pytorch/language-modeling/run_clm.py --model_name_or_path gpt2 \
--dataset_name wikitext --dataset_config_name wikitext-2-raw-v1 --do_train \
--output_dir /tmp/test-clm --per_device_train_batch_size 4 --max_steps 200
{'train_runtime': 101.9003, 'train_samples_per_second': 1.963, 'epoch': 0.69}
# DDP w/o NVLink
rm -r /tmp/test-clm; CUDA_VISIBLE_DEVICES=0,1 NCCL_P2P_DISABLE=1 torchrun \
--nproc_per_node 2 examples/pytorch/language-modeling/run_clm.py --model_name_or_path gpt2 \
--dataset_name wikitext --dataset_config_name wikitext-2-raw-v1 --do_train
--output_dir /tmp/test-clm --per_device_train_batch_size 4 --max_steps 200
{'train_runtime': 131.4367, 'train_samples_per_second': 1.522, 'epoch': 0.69}
硬件:每个 2x TITAN RTX 24GB + 2 个 NVLink 的 NVlink(在nvidia-smi topo -m
中为NV2
) 软件:pytorch-1.8-to-be
+ cuda-11.0
/ transformers==4.3.0.dev0
使用 Trainer API 进行超参数搜索
🤗 Transformers 提供了一个专为训练🤗 Transformers 模型优化的 Trainer 类,使得更容易开始训练而无需手动编写自己的训练循环。Trainer 提供了用于超参数搜索的 API。本文档展示了如何在示例中启用它。
超参数搜索后端
Trainer 目前支持四种超参数搜索后端:optuna、sigopt、raytune和wandb。
在使用超参数搜索后端之前,您应该先安装它们
pip install optuna/sigopt/wandb/ray[tune]
如何在示例中启用超参数搜索
定义超参数搜索空间,不同的后端需要不同的格式。
对于 sigopt,请参阅 sigopt object_parameter,就像下面这样:
>>> def sigopt_hp_space(trial):
... return [
... {"bounds": {"min": 1e-6, "max": 1e-4}, "name": "learning_rate", "type": "double"},
... {
... "categorical_values": ["16", "32", "64", "128"],
... "name": "per_device_train_batch_size",
... "type": "categorical",
... },
... ]
对于 optuna,请参阅 optuna object_parameter,就像下面这样:
>>> def optuna_hp_space(trial):
... return {
... "learning_rate": trial.suggest_float("learning_rate", 1e-6, 1e-4, log=True),
... "per_device_train_batch_size": trial.suggest_categorical("per_device_train_batch_size", [16, 32, 64, 128]),
... }
Optuna 提供多目标 HPO。您可以在hyperparameter_search
中传递direction
并定义自己的compute_objective
来返回多个目标值。 Pareto 前沿(List[BestRun]
)将在hyperparameter_search
中返回,您应该参考test_trainer中的测试用例TrainerHyperParameterMultiObjectOptunaIntegrationTest
。就像下面这样
>>> best_trials = trainer.hyperparameter_search(
... direction=["minimize", "maximize"],
... backend="optuna",
... hp_space=optuna_hp_space,
... n_trials=20,
... compute_objective=compute_objective,
... )
对于 raytune,请参阅 raytune object_parameter,就像下面这样:
>>> def ray_hp_space(trial):
... return {
... "learning_rate": tune.loguniform(1e-6, 1e-4),
... "per_device_train_batch_size": tune.choice([16, 32, 64, 128]),
... }
对于 wandb,请参阅 wandb object_parameter,就像下面这样:
>>> def wandb_hp_space(trial):
... return {
... "method": "random",
... "metric": {"name": "objective", "goal": "minimize"},
... "parameters": {
... "learning_rate": {"distribution": "uniform", "min": 1e-6, "max": 1e-4},
... "per_device_train_batch_size": {"values": [16, 32, 64, 128]},
... },
... }
定义一个model_init
函数并将其传递给 Trainer,例如:
>>> def model_init(trial):
... return AutoModelForSequenceClassification.from_pretrained(
... model_args.model_name_or_path,
... from_tf=bool(".ckpt" in model_args.model_name_or_path),
... config=config,
... cache_dir=model_args.cache_dir,
... revision=model_args.model_revision,
... token=True if model_args.use_auth_token else None,
... )
使用您的model_init
函数、训练参数、训练和测试数据集以及评估函数创建一个 Trainer:
>>> trainer = Trainer(
... model=None,
... args=training_args,
... train_dataset=small_train_dataset,
... eval_dataset=small_eval_dataset,
... compute_metrics=compute_metrics,
... tokenizer=tokenizer,
... model_init=model_init,
... data_collator=data_collator,
... )
调用超参数搜索,获取最佳试验参数,后端可以是"optuna"
/"sigopt"
/"wandb"
/"ray"
。方向可以是"minimize"
或"maximize"
,表示是优化更大还是更小的目标。
您可以定义自己的compute_objective
函数,如果未定义,将调用默认的compute_objective
,并将类似 f1 的评估指标的总和作为目标值返回。
>>> best_trial = trainer.hyperparameter_search(
... direction="maximize",
... backend="optuna",
... hp_space=optuna_hp_space,
... n_trials=20,
... compute_objective=compute_objective,
... )
DDP 微调的超参数搜索
目前,optuna 和 sigopt 已启用 DDP 的超参数搜索。只有排名为零的进程才会生成搜索试验并将参数传递给其他排名。
优化推理
CPU 推理
原文链接:
huggingface.co/docs/transformers/v4.37.2/en/perf_infer_cpu
通过一些优化,可以在 CPU 上高效运行大型模型推理。其中一种优化技术涉及将 PyTorch 代码编译成高性能环境(如 C++)的中间格式。另一种技术是将多个操作融合成一个内核,以减少单独运行每个操作的开销。
您将学习如何使用BetterTransformer进行更快的推理,以及如何将您的 PyTorch 代码转换为TorchScript。如果您使用 Intel CPU,还可以使用Intel Extension for PyTorch中的图优化来进一步提高推理速度。最后,学习如何使用🤗 Optimum 来加速使用 ONNX Runtime 或 OpenVINO 进行推理(如果您使用 Intel CPU)。
BetterTransformer
BetterTransformer 通过其快速路径(Transformer 函数的本机 PyTorch 专用实现)执行加速推理。快速路径执行中的两个优化是:
-
融合,将多个顺序操作组合成一个“内核”,以减少计算步骤的数量
-
跳过填充令牌的固有稀疏性,以避免与嵌套张量一起进行不必要的计算
BetterTransformer 还将所有注意力操作转换为更节省内存的缩放点积注意力。
并非所有模型都支持 BetterTransformer。查看此列表以查看模型是否支持 BetterTransformer。
在开始之前,请确保您已经安装了🤗 Optimum installed。
使用 PreTrainedModel.to_bettertransformer()方法启用 BetterTransformer:
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("bigcode/starcoder")
model.to_bettertransformer()
TorchScript
TorchScript 是一个中间 PyTorch 模型表示,可以在性能重要的生产环境中运行。您可以在 PyTorch 中训练模型,然后将其导出到 TorchScript 中,以解放模型免受 Python 性能约束。PyTorch跟踪一个模型以返回一个经过即时编译(JIT)优化的ScriptFunction
。与默认的急切模式相比,PyTorch 中的 JIT 模式通常通过操作融合等优化技术为推理提供更好的性能。
有关 TorchScript 的简要介绍,请参阅PyTorch TorchScript 简介教程。
使用 Trainer 类,您可以通过设置--jit_mode_eval
标志为 CPU 推理启用 JIT 模式:
python run_qa.py \
--model_name_or_path csarron/bert-base-uncased-squad-v1 \
--dataset_name squad \
--do_eval \
--max_seq_length 384 \
--doc_stride 128 \
--output_dir /tmp/ \
--no_cuda \
--jit_mode_eval
对于 PyTorch >= 1.14.0,JIT 模式可以使任何模型受益于预测和评估,因为jit.trace
支持字典输入。
对于 PyTorch < 1.14.0,如果模型的前向参数顺序与jit.trace
中的元组输入顺序匹配,例如问答模型,JIT 模式可以使模型受益。如果前向参数顺序与jit.trace
中的元组输入顺序不匹配,例如文本分类模型,jit.trace
将失败,我们在此处捕获此异常以使其回退。使用日志记录通知用户。
IPEX 图优化
Intel® Extension for PyTorch (IPEX)为 Intel CPU 的 JIT 模式提供进一步优化,并建议将其与 TorchScript 结合使用以获得更快的性能。IPEX 图优化融合了多头注意力、Concat Linear、Linear + Add、Linear + Gelu、Add + LayerNorm 等操作。
要利用这些图优化,请确保已安装 IPEX installed。
pip install intel_extension_for_pytorch
在 Trainer 类中设置--use_ipex
和--jit_mode_eval
标志以启用带有图优化的 JIT 模式:
python run_qa.py \
--model_name_or_path csarron/bert-base-uncased-squad-v1 \
--dataset_name squad \
--do_eval \
--max_seq_length 384 \
--doc_stride 128 \
--output_dir /tmp/ \
--no_cuda \
--use_ipex \
--jit_mode_eval
🤗 Optimum
在Optimum Inference with ONNX Runtime指南中了解有关使用 ORT 与🤗 Optimum 的更多详细信息。本节仅提供了一个简短且简单的示例。
ONNX Runtime (ORT)是一个模型加速器,默认情况下在 CPU 上运行推理。ORT 受🤗 Optimum 支持,可以在🤗 Transformers 中使用,而无需对您的代码进行太多更改。您只需要将🤗 Transformers 的AutoClass
替换为其等效的ORTModel以解决您正在解决的任务,并加载一个 ONNX 格式的检查点。
例如,如果您正在运行问题回答任务的推理,加载包含model.onnx
文件的optimum/roberta-base-squad2检查点:
from transformers import AutoTokenizer, pipeline
from optimum.onnxruntime import ORTModelForQuestionAnswering
model = ORTModelForQuestionAnswering.from_pretrained("optimum/roberta-base-squad2")
tokenizer = AutoTokenizer.from_pretrained("deepset/roberta-base-squad2")
onnx_qa = pipeline("question-answering", model=model, tokenizer=tokenizer)
question = "What's my name?"
context = "My name is Philipp and I live in Nuremberg."
pred = onnx_qa(question, context)
如果您有 Intel CPU,请查看🤗 Optimum Intel,该支持各种压缩技术(量化、剪枝、知识蒸馏)和将模型转换为OpenVINO格式以获得更高性能推理的工具。
GPU 推理
原文:
huggingface.co/docs/transformers/v4.37.2/en/perf_infer_gpu_one
与 CPU 不同,GPU 是机器学习的标准硬件选择,因为它们针对内存带宽和并行性进行了优化。为了跟上现代模型的更大尺寸或在现有和较旧的硬件上运行这些大型模型,您可以使用几种优化方法来加速 GPU 推理。在本指南中,您将学习如何使用 FlashAttention-2(一种更节省内存的注意力机制)、BetterTransformer(PyTorch 本地快速执行路径)和 bitsandbytes 将模型量化为较低精度。最后,学习如何使用🤗 Optimum 在 Nvidia 和 AMD GPU 上加速推理。
这里描述的大多数优化也适用于多 GPU 设置!
FlashAttention-2
FlashAttention-2 是实验性的,未来版本可能会发生较大变化。
FlashAttention-2是标准注意力机制的更快、更高效的实现,可以通过以下方式显著加速推理:
-
此外,可以通过在序列长度上并行化注意力计算来优化
-
将工作分区在 GPU 线程之间,以减少它们之间的通信和共享内存读/写
目前支持以下架构的 FlashAttention-2:
您可以通过打开 GitHub Issue 或 Pull Request 来请求为另一个模型添加 FlashAttention-2 支持。
在开始之前,请确保已安装 FlashAttention-2。
NVIDIAAMD
pip install flash-attn --no-build-isolation
我们强烈建议参考详细的安装说明以了解更多支持的硬件和数据类型!
要启用 FlashAttention-2,请将参数attn_implementation="flash_attention_2"
传递给 from_pretrained():
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, LlamaForCausalLM
model_id = "tiiuae/falcon-7b"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
model_id,
torch_dtype=torch.bfloat16,
attn_implementation="flash_attention_2",
)
只有当模型的 dtype 为fp16
或bf16
时,才能使用 FlashAttention-2。在使用 FlashAttention-2 之前,请确保将模型转换为适当的 dtype 并加载到支持的设备上。
您还可以设置use_flash_attention_2=True
来启用 FlashAttention-2,但已被弃用,推荐使用attn_implementation="flash_attention_2"
。
FlashAttention-2 可以与其他优化技术(如量化)结合,以进一步加速推理。例如,您可以将 FlashAttention-2 与 8 位或 4 位量化结合使用:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, LlamaForCausalLM
model_id = "tiiuae/falcon-7b"
tokenizer = AutoTokenizer.from_pretrained(model_id)
# load in 8bit
model = AutoModelForCausalLM.from_pretrained(
model_id,
load_in_8bit=True,
attn_implementation="flash_attention_2",
)
# load in 4bit
model = AutoModelForCausalLM.from_pretrained(
model_id,
load_in_4bit=True,
attn_implementation="flash_attention_2",
)
预期的加速
您可以从推理中获得相当大的加速,特别是对于具有长序列的输入。但是,由于 FlashAttention-2 不支持使用填充令牌计算注意力分数,因此在序列包含填充令牌时,您必须手动填充/取消填充注意力分数以进行批量推理。这会导致使用填充令牌进行批量生成时出现显着减速。
为了克服这一点,在训练期间应该使用不带填充令牌的 FlashAttention-2(通过打包数据集或连接序列直到达到最大序列长度)。
对于在tiiuae/falcon-7b上进行单次前向传递,序列长度为 4096,各种批量大小且没有填充令牌,预期的加速是:
对于在meta-llama/Llama-7b-hf上进行单次前向传递,序列长度为 4096,各种批量大小且没有填充令牌,预期的加速是:
对于具有填充令牌的序列(使用填充令牌生成),您需要取消填充/填充输入序列以正确计算注意力分数。对于相对较小的序列长度,单次前向传递会产生额外开销,导致轻微加速(在下面的示例中,输入的 30%填充有填充令牌):
但是对于更大的序列长度,您可以期望获得更多的加速效益:
FlashAttention 更具内存效率,这意味着您可以在更大的序列长度上进行训练,而不会遇到内存不足的问题。对于更大的序列长度,您可以将内存使用量降低多达 20 倍。查看flash-attention存储库以获取更多详细信息。
PyTorch 缩放点积注意力
PyTorch 的torch.nn.functional.scaled_dot_product_attention
(SDPA)也可以在底层调用 FlashAttention 和内存高效的注意力核。当可用实现时,SDPA 支持目前正在 Transformers 中本地添加,并且在torch>=2.1.1
时默认用于torch
。
目前,Transformers 支持以下架构的 SDPA 推理和训练:
FlashAttention 只能用于具有fp16
或bf16
torch 类型的模型,因此请确保首先将您的模型转换为适当的类型。
默认情况下,SDPA 选择最高效的可用内核,但您可以使用torch.backends.cuda.sdp_kernel
作为上下文管理器来检查在给定设置(硬件、问题大小)中是否有可用的后端:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("facebook/opt-350m")
model = AutoModelForCausalLM.from_pretrained("facebook/opt-350m", torch_dtype=torch.float16).to("cuda")
# convert the model to BetterTransformer
model.to_bettertransformer()
input_text = "Hello my dog is cute and"
inputs = tokenizer(input_text, return_tensors="pt").to("cuda")
+ with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=False, enable_mem_efficient=False):
outputs = model.generate(**inputs)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
如果您看到下面的回溯中有错误,请尝试使用 PyTorch 的夜间版本,这可能对 FlashAttention 有更广泛的覆盖范围:
RuntimeError: No available kernel. Aborting execution.
# install PyTorch nightly
pip3 install -U --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cu118
BetterTransformer
一些 BetterTransformer 功能正在被上游到 Transformers,支持本机torch.nn.scaled_dot_product_attention
。BetterTransformer 仍然比 Transformers SDPA 集成具有更广泛的覆盖范围,但您可以期望越来越多的架构在 Transformers 中本地支持 SDPA。
查看我们在PyTorch 2.0 中使用 BetterTransformer 和缩放点积注意力的开箱即用加速和内存节省中的基准测试,并在BetterTransformer博客文章中了解更多关于快速执行的信息。
BetterTransformer 通过其快速路径(Transformer 函数的本机 PyTorch 专用实现)执行加速推断。快速路径执行中的两个优化是:
-
融合,将多个连续操作组合成一个单一的“内核”,以减少计算步骤的数量
-
跳过填充令牌的固有稀疏性,以避免使用嵌套张量进行不必要的计算
BetterTransformer 还将所有注意力操作转换为更节省内存的scaled dot product attention (SDPA),并在底层调用优化的内核,如FlashAttention。
在开始之前,请确保您已安装🤗 Optimum (已安装)。
然后,您可以使用 PreTrainedModel.to_bettertransformer()方法启用 BetterTransformer:
model = model.to_bettertransformer()
您可以使用 reverse_bettertransformer()方法返回原始的 Transformers 模型。在保存模型之前,应该使用这个方法来使用规范的 Transformers 建模:
model = model.reverse_bettertransformer()
model.save_pretrained("saved_model")
bitsandbytes
bitsandbytes 是一个包含对 4 位和 8 位量化支持的量化库。与其原生全精度版本相比,量化可以减小模型大小,使其更容易适应内存有限的 GPU。
确保您已安装 bitsandbytes 和🤗 Accelerate:
# these versions support 8-bit and 4-bit
pip install bitsandbytes>=0.39.0 accelerate>=0.20.0
# install Transformers
pip install transformers
4 位
要在 4 位模型中进行推断,使用load_in_4bit
参数。device_map
参数是可选的,但我们建议将其设置为"auto"
,以便🤗 Accelerate 根据环境中的可用资源自动高效地分配模型。
from transformers import AutoModelForCausalLM
model_name = "bigscience/bloom-2b5"
model_4bit = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto", load_in_4bit=True)
要在多个 GPU 上加载 4 位模型进行推断,您可以控制要为每个 GPU 分配多少 GPU RAM。例如,将 600MB 的内存分配给第一个 GPU,将 1GB 的内存分配给第二个 GPU:
max_memory_mapping = {0: "600MB", 1: "1GB"}
model_name = "bigscience/bloom-3b"
model_4bit = AutoModelForCausalLM.from_pretrained(
model_name, device_map="auto", load_in_4bit=True, max_memory=max_memory_mapping
)
8 位
如果您对 8 位量化的概念感兴趣并想了解更多信息,请阅读Hugging Face Transformers、Accelerate 和 bitsandbytes 使用规模化变压器进行 8 位矩阵乘法的初步介绍博客文章。
要在 8 位模型中进行推断,使用load_in_8bit
参数。device_map
参数是可选的,但我们建议将其设置为"auto"
,以便🤗 Accelerate 根据环境中的可用资源自动高效地分配模型:
from transformers import AutoModelForCausalLM
model_name = "bigscience/bloom-2b5"
model_8bit = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto", load_in_8bit=True)
如果您要加载 8 位模型进行文本生成,应该使用 generate()方法,而不是未经优化的 Pipeline 函数,后者对 8 位模型不适用且速度较慢。一些采样策略,如核采样,也不受 Pipeline 支持。您还应该将所有输入放在与模型相同的设备上:
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = "bigscience/bloom-2b5"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model_8bit = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto", load_in_8bit=True)
prompt = "Hello, my llama is cute"
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
generated_ids = model.generate(**inputs)
outputs = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)
要在多个 GPU 上加载 4 位模型进行推断,您可以控制要为每个 GPU 分配多少 GPU RAM。例如,要将 1GB 内存分配给第一个 GPU,将 2GB 内存分配给第二个 GPU:
max_memory_mapping = {0: "1GB", 1: "2GB"}
model_name = "bigscience/bloom-3b"
model_8bit = AutoModelForCausalLM.from_pretrained(
model_name, device_map="auto", load_in_8bit=True, max_memory=max_memory_mapping
)
随意尝试在 Google Colab 的免费 GPU 上运行一个拥有 110 亿参数的T5 模型或 30 亿参数的BLOOM 模型进行推断!
🤗 Optimum
了解有关在NVIDIA GPU 上进行加速推断和AMD GPU 上进行加速推断的指南中使用 ORT 的更多详细信息。本节仅提供简要且简单的示例。
ONNX Runtime(ORT)是一个模型加速器,支持在 Nvidia GPU 和使用ROCm堆栈的 AMD GPU 上进行加速推断。ORT 使用优化技术,如将常见操作融合为单个节点和常量折叠,以减少执行的计算量并加快推断速度。ORT 还将计算密集型操作放在 GPU 上,其余操作放在 CPU 上,智能地在两个设备之间分配工作负载。
ORT 受🤗 Optimum 支持,可以在🤗 Transformers 中使用。您需要使用一个ORTModel来解决您的任务,并指定provider
参数,可以设置为CUDAExecutionProvider
、ROCMExecutionProvider
或TensorrtExecutionProvider
。如果要加载尚未导出为 ONNX 的模型,可以设置export=True
将您的模型即时转换为 ONNX 格式:
from optimum.onnxruntime import ORTModelForSequenceClassification
ort_model = ORTModelForSequenceClassification.from_pretrained(
"distilbert-base-uncased-finetuned-sst-2-english",
export=True,
provider="CUDAExecutionProvider",
)
现在您可以自由地使用模型进行推断:
from optimum.pipelines import pipeline
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased-finetuned-sst-2-english")
pipeline = pipeline(task="text-classification", model=ort_model, tokenizer=tokenizer, device="cuda:0")
result = pipeline("Both the music and visual were astounding, not to mention the actors performance.")
结合优化
通常可以结合上述描述的多种优化技术,以获得最佳的推断性能。例如,您可以加载一个 4 位模型,然后启用带有 FlashAttention 的 BetterTransformer:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
# load model in 4-bit
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16
)
tokenizer = AutoTokenizer.from_pretrained("facebook/opt-350m")
model = AutoModelForCausalLM.from_pretrained("facebook/opt-350m", quantization_config=quantization_config)
# enable BetterTransformer
model = model.to_bettertransformer()
input_text = "Hello my dog is cute and"
inputs = tokenizer(input_text, return_tensors="pt").to("cuda")
# enable FlashAttention
with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=False, enable_mem_efficient=False):
outputs = model.generate(**inputs)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
实例化一个大模型
当您想要使用非常大的预训练模型时,一个挑战是尽量减少 RAM 的使用。来自 PyTorch 的通常工作流程是:
-
用随机权重创建您的模型。
-
加载您的预训练权重。
-
将这些预训练权重放入您的随机模型中。
步骤 1 和 2 都需要内存中的完整模型版本,在大多数情况下这不是问题,但是如果您的模型开始占用数千兆字节,这两个副本可能会使您的 RAM 不足。更糟糕的是,如果您使用torch.distributed
启动分布式训练,每个进程都会加载预训练模型并将这两个副本存储在 RAM 中。
请注意,随机创建的模型是用“空”张量初始化的,这些张量占用内存空间而不填充它(因此随机值是在给定时间内内存块中的内容)。适合模型/参数实例化的适当分布(例如正态分布)的随机初始化仅在第 3 步对未初始化的权重执行,以尽可能快地完成!
在本指南中,我们探讨了 Transformers 提供的解决此问题的解决方案。请注意,这是一个正在积极发展的领域,因此这里解释的 API 可能在未来略有变化。
分片检查点
自版本 4.18.0 以来,占用超过 10GB 空间的模型检查点会自动分片成较小的部分。在执行model.save_pretrained(save_dir)
时,您将得到几个部分检查点(每个大小均小于 10GB)和一个将参数名称映射到存储文件的索引。
您可以使用max_shard_size
参数控制分片之前的最大大小,因此为了举例,我们将使用一个具有小分片大小的正常大小模型:让我们使用传统的 BERT 模型。
from transformers import AutoModel
model = AutoModel.from_pretrained("bert-base-cased")
如果您使用 save_pretrained()保存它,您将获得一个新文件夹,其中包含模型的配置和权重:
>>> import os
>>> import tempfile
>>> with tempfile.TemporaryDirectory() as tmp_dir:
... model.save_pretrained(tmp_dir)
... print(sorted(os.listdir(tmp_dir)))
['config.json', 'pytorch_model.bin']
现在让我们使用最大分片大小为 200MB:
>>> with tempfile.TemporaryDirectory() as tmp_dir:
... model.save_pretrained(tmp_dir, max_shard_size="200MB")
... print(sorted(os.listdir(tmp_dir)))
['config.json', 'pytorch_model-00001-of-00003.bin', 'pytorch_model-00002-of-00003.bin', 'pytorch_model-00003-of-00003.bin', 'pytorch_model.bin.index.json']
除了模型的配置之外,我们看到三个不同的权重文件,以及一个index.json
文件,这是我们的索引。可以使用 from_pretrained()方法完全重新加载这样的检查点:
>>> with tempfile.TemporaryDirectory() as tmp_dir:
... model.save_pretrained(tmp_dir, max_shard_size="200MB")
... new_model = AutoModel.from_pretrained(tmp_dir)
这样做大模型的主要优势在于,在上述工作流程的第 2 步中,检查点的每个分片在前一个分片之后加载,将 RAM 中的内存使用限制在模型大小加上最大分片大小的大小。
在幕后,索引文件用于确定检查点中的键以及相应权重存储的位置。我们可以像加载任何 json 一样加载该索引并获得一个字典:
>>> import json
>>> with tempfile.TemporaryDirectory() as tmp_dir:
... model.save_pretrained(tmp_dir, max_shard_size="200MB")
... with open(os.path.join(tmp_dir, "pytorch_model.bin.index.json"), "r") as f:
... index = json.load(f)
>>> print(index.keys())
dict_keys(['metadata', 'weight_map'])
元数据目前只包含模型的总大小。我们计划在未来添加其他信息:
>>> index["metadata"]
{'total_size': 433245184}
权重映射是此索引的主要部分,它将每个参数名称(通常在 PyTorch 模型state_dict
中找到)映射到其存储的文件:
>>> index["weight_map"]
{'embeddings.LayerNorm.bias': 'pytorch_model-00001-of-00003.bin',
'embeddings.LayerNorm.weight': 'pytorch_model-00001-of-00003.bin',
...
如果您想在不使用 from_pretrained()(就像您为完整检查点执行model.load_state_dict()
一样)的情况下直接加载这样的分片检查点,您应该使用 load_sharded_checkpoint():
>>> from transformers.modeling_utils import load_sharded_checkpoint
>>> with tempfile.TemporaryDirectory() as tmp_dir:
... model.save_pretrained(tmp_dir, max_shard_size="200MB")
... load_sharded_checkpoint(model, tmp_dir)
低内存加载
分片检查点减少了上述工作流程第 2 步中的内存使用,但为了在低内存环境中使用该模型,我们建议利用基于 Accelerate 库的工具。
请阅读以下指南以获取更多信息:使用 Accelerate 进行大型模型加载
调试
在多个 GPU 上进行训练可能是一个棘手的任务,无论是遇到安装问题还是 GPU 之间的通信问题。这个调试指南涵盖了一些可能遇到的问题以及如何解决它们。
DeepSpeed CUDA 安装
如果您正在使用 DeepSpeed,您可能已经使用以下命令安装了它。
pip install deepspeed
DeepSpeed 编译 CUDA C++代码,当构建需要 CUDA 的 PyTorch 扩展时,这可能是错误的潜在来源。这些错误取决于 CUDA 在您的系统上的安装方式,本节重点介绍了使用* CUDA 10.2 *构建的 PyTorch。
对于任何其他安装问题,请提出问题给 DeepSpeed 团队。
不同的 CUDA 工具包
PyTorch 自带其自己的 CUDA 工具包,但要使用 DeepSpeed 与 PyTorch,您需要在整个系统中安装相同版本的 CUDA。例如,如果您在 Python 环境中安装了cudatoolkit==10.2
的 PyTorch,则您还需要在整个系统中安装 CUDA 10.2。如果您的系统中没有安装 CUDA,则应首先安装它。
确切的位置可能因系统而异,但在许多 Unix 系统上,usr/local/cuda-10.2
是最常见的位置。当 CUDA 正确设置并添加到您的PATH
环境变量时,您可以使用以下命令找到安装位置:
which nvcc
多个 CUDA 工具包
您的系统中可能安装了多个 CUDA 工具包。
/usr/local/cuda-10.2
/usr/local/cuda-11.0
通常,软件包安装程序会将路径设置为最后安装的版本。如果软件包构建失败,因为找不到正确的 CUDA 版本(尽管它已经在整个系统中安装),则需要配置PATH
和LD_LIBRARY_PATH
环境变量以指向正确的路径。
首先查看这些环境变量的内容:
echo $PATH
echo $LD_LIBRARY_PATH
PATH
列出了可执行文件的位置,LD_LIBRARY_PATH
列出了共享库的查找位置。较早的条目优先于后续的条目,:
用于分隔多个条目。为了告诉构建程序要找到您想要的特定 CUDA 工具包,插入正确的路径以首先列出。此命令在现有值之前而不是覆盖现有值。
# adjust the version and full path if needed
export PATH=/usr/local/cuda-10.2/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-10.2/lib64:$LD_LIBRARY_PATH
此外,您还应检查您分配的目录是否实际存在。lib64
子目录包含各种 CUDA.so
对象(如libcudart.so
),虽然您的系统不太可能以不同的名称命名它们,但您应检查实际名称并相应更改。
较旧的 CUDA 版本
有时,较旧的 CUDA 版本可能拒绝与更新的编译器一起构建。例如,如果您有gcc-9
但 CUDA 需要gcc-7
。通常,安装最新的 CUDA 工具包可以支持更新的编译器。
您还可以安装一个较旧版本的编译器,以及您当前使用的一个(或者可能已经安装,但默认情况下未使用,构建系统无法看到)。要解决此问题,您可以创建一个符号链接,以便构建系统能够看到较旧的编译器。
# adapt the path to your system
sudo ln -s /usr/bin/gcc-7 /usr/local/cuda-10.2/bin/gcc
sudo ln -s /usr/bin/g++-7 /usr/local/cuda-10.2/bin/g++
多 GPU 网络问题调试
当使用DistributedDataParallel
和多个 GPU 进行训练或推理时,如果遇到进程和/或节点之间的互通问题,您可以使用以下脚本来诊断网络问题。
wget https://raw.githubusercontent.com/huggingface/transformers/main/scripts/distributed/torch-distributed-gpu-test.py
例如,要测试 2 个 GPU 如何交互,请执行:
python -m torch.distributed.run --nproc_per_node 2 --nnodes 1 torch-distributed-gpu-test.py
如果两个进程都可以相互通信并分配 GPU 内存,每个进程都将打印 OK 状态。
对于更多的 GPU 或节点,请调整脚本中的参数。
您将在诊断脚本中找到更多详细信息,甚至可以了解如何在 SLURM 环境中运行它的方法。
另一个调试级别是添加NCCL_DEBUG=INFO
环境变量如下:
NCCL_DEBUG=INFO python -m torch.distributed.run --nproc_per_node 2 --nnodes 1 torch-distributed-gpu-test.py
这将输出大量与 NCCL 相关的调试信息,如果发现有问题报告,您可以在网上搜索。或者如果您不确定如何解释输出,可以在 Issue 中分享日志文件。
下溢和溢出检测
此功能目前仅适用于 PyTorch。
对于多 GPU 训练,需要 DDP(torch.distributed.launch
)。
此功能可与任何基于nn.Module
的模型一起使用。
如果开始出现loss=NaN
或模型由于激活或权重中的inf
或nan
而表现出其他异常行为,需要找出第一个下溢或溢出发生的位置以及导致其发生的原因。幸运的是,您可以通过激活一个特殊模块来轻松实现自动检测。
如果您正在使用 Trainer,您只需要添加:
--debug underflow_overflow
除了正常的命令行参数外,在创建 TrainingArguments 对象时,也可以传递debug="underflow_overflow"
。
如果您正在使用自己的训练循环或另一个 Trainer,可以通过以下方式实现相同的效果:
from transformers.debug_utils import DebugUnderflowOverflow
debug_overflow = DebugUnderflowOverflow(model)
DebugUnderflowOverflow 会在模型中插入钩子,每次前向调用后立即测试输入和输出变量以及相应模块的权重。一旦在激活或权重的至少一个元素中检测到inf
或nan
,程序将断言并打印类似于这样的报告(这是在 fp16 混合精度下使用google/mt5-small
捕获的)。
Detected inf/nan during batch_number=0
Last 21 forward frames:
abs min abs max metadata
encoder.block.1.layer.1.DenseReluDense.dropout Dropout
0.00e+00 2.57e+02 input[0]
0.00e+00 2.85e+02 output [...]
encoder.block.2.layer.0 T5LayerSelfAttention
6.78e-04 3.15e+03 input[0]
2.65e-04 3.42e+03 output[0]
None output[1]
2.25e-01 1.00e+04 output[2]
encoder.block.2.layer.1.layer_norm T5LayerNorm
8.69e-02 4.18e-01 weight
2.65e-04 3.42e+03 input[0]
1.79e-06 4.65e+00 output
encoder.block.2.layer.1.DenseReluDense.wi_0 Linear
2.17e-07 4.50e+00 weight
1.79e-06 4.65e+00 input[0]
2.68e-06 3.70e+01 output
encoder.block.2.layer.1.DenseReluDense.wi_1 Linear
8.08e-07 2.66e+01 weight
1.79e-06 4.65e+00 input[0]
1.27e-04 2.37e+02 output
encoder.block.2.layer.1.DenseReluDense.dropout Dropout
0.00e+00 8.76e+03 input[0]
0.00e+00 9.74e+03 output
encoder.block.2.layer.1.DenseReluDense.wo Linear
1.01e-06 6.44e+00 weight
0.00e+00 9.74e+03 input[0]
3.18e-04 6.27e+04 output
encoder.block.2.layer.1.DenseReluDense T5DenseGatedGeluDense
1.79e-06 4.65e+00 input[0]
3.18e-04 6.27e+04 output
encoder.block.2.layer.1.dropout Dropout
3.18e-04 6.27e+04 input[0]
0.00e+00 inf output
示例输出已经为简洁起见进行了修剪。
第二列显示了绝对最大元素的值,因此如果您仔细查看最后几个帧,输入和输出的范围在1e4
。因此,当此训练在 fp16 混合精度下进行时,最后一步发生了溢出(因为在fp16
下,在inf
之前的最大数字是64e3
)。为了避免在fp16
下发生溢出,激活必须保持远低于1e4
,因为1e4 * 1e4 = 1e8
,因此任何具有大激活的矩阵乘法都将导致数值溢出条件。
在跟踪的最开始,您可以发现问题发生在哪个批次号(这里Detected inf/nan during batch_number=0
表示问题发生在第一个批次)。
每个报告的帧都以声明相应模块的完全限定条目开头。如果我们只看这个帧:
encoder.block.2.layer.1.layer_norm T5LayerNorm
8.69e-02 4.18e-01 weight
2.65e-04 3.42e+03 input[0]
1.79e-06 4.65e+00 output
在这里,encoder.block.2.layer.1.layer_norm
表示它是编码器第二块的第一层的层归一化。而forward
的具体调用是T5LayerNorm
。
让我们看一下报告的最后几个帧:
Detected inf/nan during batch_number=0
Last 21 forward frames:
abs min abs max metadata [...]
encoder.block.2.layer.1.DenseReluDense.wi_0 Linear
2.17e-07 4.50e+00 weight
1.79e-06 4.65e+00 input[0]
2.68e-06 3.70e+01 output
encoder.block.2.layer.1.DenseReluDense.wi_1 Linear
8.08e-07 2.66e+01 weight
1.79e-06 4.65e+00 input[0]
1.27e-04 2.37e+02 output
encoder.block.2.layer.1.DenseReluDense.wo Linear
1.01e-06 6.44e+00 weight
0.00e+00 9.74e+03 input[0]
3.18e-04 6.27e+04 output
encoder.block.2.layer.1.DenseReluDense T5DenseGatedGeluDense
1.79e-06 4.65e+00 input[0]
3.18e-04 6.27e+04 output
encoder.block.2.layer.1.dropout Dropout
3.18e-04 6.27e+04 input[0]
0.00e+00 inf output
最后一个帧报告了Dropout.forward
函数,第一个条目是唯一输入,第二个是唯一输出。您可以看到它是从DenseReluDense
类内部的dropout
属性调用的。我们可以看到它发生在第二块的第一层,在第一个批次期间。最后,绝对最大的输入元素是6.27e+04
,输出也是inf
。
您可以在这里看到,T5DenseGatedGeluDense.forward
的输出激活结果,其绝对最大值约为 62.7K,非常接近 fp16 的 64K 顶限。在下一个帧中,我们有Dropout
,它在将一些元素归零后重新归一化权重,将绝对最大值推到超过 64K,导致溢出(inf
)。
正如您所看到的,当数字开始变得非常大时,我们需要查看前面的帧以了解情况。
让我们将报告与models/t5/modeling_t5.py
中的代码进行匹配:
class T5DenseGatedGeluDense(nn.Module):
def __init__(self, config):
super().__init__()
self.wi_0 = nn.Linear(config.d_model, config.d_ff, bias=False)
self.wi_1 = nn.Linear(config.d_model, config.d_ff, bias=False)
self.wo = nn.Linear(config.d_ff, config.d_model, bias=False)
self.dropout = nn.Dropout(config.dropout_rate)
self.gelu_act = ACT2FN["gelu_new"]
def forward(self, hidden_states):
hidden_gelu = self.gelu_act(self.wi_0(hidden_states))
hidden_linear = self.wi_1(hidden_states)
hidden_states = hidden_gelu * hidden_linear
hidden_states = self.dropout(hidden_states)
hidden_states = self.wo(hidden_states)
return hidden_states
现在很容易看到dropout
调用以及所有先前的调用。
由于检测发生在前向挂钩中,这些报告将在每个forward
返回后立即打印。
回到完整报告,要对其进行操作并解决问题,我们需要向上移动几帧,找到数字开始增加的地方,并且很可能在这里切换到fp32
模式,以便在乘法或求和时数字不会溢出。当然,可能还有其他解决方案。例如,我们可以在将原始forward
移入辅助包装器后,暂时关闭amp
,如下所示:
def _forward(self, hidden_states):
hidden_gelu = self.gelu_act(self.wi_0(hidden_states))
hidden_linear = self.wi_1(hidden_states)
hidden_states = hidden_gelu * hidden_linear
hidden_states = self.dropout(hidden_states)
hidden_states = self.wo(hidden_states)
return hidden_states
import torch
def forward(self, hidden_states):
if torch.is_autocast_enabled():
with torch.cuda.amp.autocast(enabled=False):
return self._forward(hidden_states)
else:
return self._forward(hidden_states)
由于自动检测器仅报告完整帧的输入和输出,一旦您知道要查找的位置,您可能还想分析任何特定forward
函数的中间阶段。在这种情况下,您可以使用detect_overflow
辅助函数将检测器注入到您想要的位置,例如:
from debug_utils import detect_overflow
class T5LayerFF(nn.Module):
[...]
def forward(self, hidden_states):
forwarded_states = self.layer_norm(hidden_states)
detect_overflow(forwarded_states, "after layer_norm")
forwarded_states = self.DenseReluDense(forwarded_states)
detect_overflow(forwarded_states, "after DenseReluDense")
return hidden_states + self.dropout(forwarded_states)
您可以看到我们添加了 2 个这样的内容,现在我们跟踪forwarded_states
中是否在中间某处检测到了inf
或nan
。
实际上,检测器已经报告了这些,因为上面示例中的每个调用都是一个nn.Module
,但是假设如果您有一些本地直接计算,这就是您将如何执行的方式。
此外,如果您在自己的代码中实例化调试器,您可以调整从默认值打印的帧数,例如:
from transformers.debug_utils import DebugUnderflowOverflow
debug_overflow = DebugUnderflowOverflow(model, max_frames_to_save=100)
特定批次的绝对最小值和最大值跟踪
相同的调试类可以用于关闭下溢/溢出检测功能的每批次跟踪。
假设您想要观察给定批次的每个forward
调用的所有成分的绝对最小值和最大值,并且仅对批次 1 和 3 执行此操作。然后,您可以将此类实例化为:
debug_overflow = DebugUnderflowOverflow(model, trace_batch_nums=[1, 3])
现在,完整的批次 1 和 3 将使用与下溢/溢出检测器相同的格式进行跟踪。
批次是从 0 开始索引的。
如果您知道程序在某个特定批次号之后开始表现不当,那么您可以直接快进到该区域。以下是这种配置的示例截断输出:
*** Starting batch number=1 ***
abs min abs max metadata
shared Embedding
1.01e-06 7.92e+02 weight
0.00e+00 2.47e+04 input[0]
5.36e-05 7.92e+02 output
[...]
decoder.dropout Dropout
1.60e-07 2.27e+01 input[0]
0.00e+00 2.52e+01 output
decoder T5Stack
not a tensor output
lm_head Linear
1.01e-06 7.92e+02 weight
0.00e+00 1.11e+00 input[0]
6.06e-02 8.39e+01 output
T5ForConditionalGeneration
not a tensor output
*** Starting batch number=3 ***
abs min abs max metadata
shared Embedding
1.01e-06 7.92e+02 weight
0.00e+00 2.78e+04 input[0]
5.36e-05 7.92e+02 output
[...]
在这里,您将获得大量的帧转储 - 与您模型中的前向调用数量一样多,因此可能或可能不是您想要的,但有时它可能比普通调试器更容易用于调试目的。例如,如果问题开始在批次号为 150 时发生。因此,您可以为批次 149 和 150 转储跟踪,并比较数字开始发散的地方。
您还可以指定在哪个批次号之后停止训练,例如:
debug_overflow = DebugUnderflowOverflow(model, trace_batch_nums=[1, 3], abort_after_batch_num=3)
TensorFlow 模型的 XLA 集成
加速线性代数,简称 XLA,是用于加速 TensorFlow 模型运行时的编译器。来自官方文档:
XLA(加速线性代数)是一个专门用于线性代数的编译器,可以加速 TensorFlow 模型,可能不需要源代码更改。
在 TensorFlow 中使用 XLA 很简单 - 它已经打包在tensorflow
库中,并且可以通过任何创建图形函数(例如tf.function
)中的jit_compile
参数触发。当使用 Keras 方法如fit()
和predict()
时,您可以通过将jit_compile
参数传递给model.compile()
来简单启用 XLA。但是,XLA 不仅限于这些方法 - 它还可以用于加速任何任意的tf.function
。
🤗 Transformers 中的几个 TensorFlow 方法已经重写为与 XLA 兼容,包括用于模型的文本生成,如GPT2、T5和OPT,以及用于语音处理的模型,如Whisper。
在🤗 Transformers 内部的 TensorFlow 文本生成模型中,加速的确切数量非常依赖于模型,我们注意到速度提升了约 100 倍。本文将解释如何在这些模型中使用 XLA 来获得最大的性能。我们还将提供额外资源的链接,如果您有兴趣了解更多关于基准测试和我们在 XLA 集成背后的设计理念。
使用 XLA 运行 TF 函数
让我们考虑以下 TensorFlow 模型:
import tensorflow as tf
model = tf.keras.Sequential(
[tf.keras.layers.Dense(10, input_shape=(10,), activation="relu"), tf.keras.layers.Dense(5, activation="softmax")]
)
上述模型接受维度为(10, )
的输入。我们可以使用该模型来运行前向传递,如下所示:
# Generate random inputs for the model.
batch_size = 16
input_vector_dim = 10
random_inputs = tf.random.normal((batch_size, input_vector_dim))
# Run a forward pass.
_ = model(random_inputs)
为了使用 XLA 编译函数运行前向传递,我们需要执行以下操作:
xla_fn = tf.function(model, jit_compile=True)
_ = xla_fn(random_inputs)
model
的默认call()
函数用于编译 XLA 图。但是,如果有任何其他模型函数您想要编译成 XLA,也是可能的,例如:
my_xla_fn = tf.function(model.my_xla_fn, jit_compile=True)
使用🤗 Transformers 中的 XLA 运行 TF 文本生成模型
要在🤗 Transformers 内启用 XLA 加速生成,您需要安装最新版本的transformers
。您可以通过运行以下命令来安装:
pip install transformers --upgrade
然后您可以运行以下代码:
import tensorflow as tf
from transformers import AutoTokenizer, TFAutoModelForCausalLM
# Will error if the minimal version of Transformers is not installed.
from transformers.utils import check_min_version
check_min_version("4.21.0")
tokenizer = AutoTokenizer.from_pretrained("gpt2", padding_side="left", pad_token="</s>")
model = TFAutoModelForCausalLM.from_pretrained("gpt2")
input_string = ["TensorFlow is"]
# One line to create an XLA generation function
xla_generate = tf.function(model.generate, jit_compile=True)
tokenized_input = tokenizer(input_string, return_tensors="tf")
generated_tokens = xla_generate(**tokenized_input, num_beams=2)
decoded_text = tokenizer.decode(generated_tokens[0], skip_special_tokens=True)
print(f"Generated -- {decoded_text}")
# Generated -- TensorFlow is an open-source, open-source, distributed-source application # framework for the
正如您可以注意到的,在generate()
上启用 XLA 只是一行代码。其余代码保持不变。但是,上面代码片段中有一些特定于 XLA 的注意事项。您需要注意这些才能实现 XLA 带来的加速。我们将在下一节中讨论这些。
需要注意的事项
当您首次执行启用 XLA 的函数(如上面的xla_generate()
)时,它将内部尝试推断计算图,这是耗时的。这个过程被称为“跟踪”。
您可能会注意到生成时间不够快。连续调用xla_generate()
(或任何其他启用 XLA 的函数)不需要推断计算图,只要函数的输入遵循最初构建计算图时的相同形状。虽然对于具有固定输入形状的模态(例如图像)这不是问题,但如果您正在处理具有可变输入形状的模态(例如文本),则必须注意。
为了确保xla_generate()
始终使用相同的输入形状,您可以在调用分词器时指定padding
参数。
import tensorflow as tf
from transformers import AutoTokenizer, TFAutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("gpt2", padding_side="left", pad_token="</s>")
model = TFAutoModelForCausalLM.from_pretrained("gpt2")
input_string = ["TensorFlow is"]
xla_generate = tf.function(model.generate, jit_compile=True)
# Here, we call the tokenizer with padding options.
tokenized_input = tokenizer(input_string, pad_to_multiple_of=8, padding=True, return_tensors="tf")
generated_tokens = xla_generate(**tokenized_input, num_beams=2)
decoded_text = tokenizer.decode(generated_tokens[0], skip_special_tokens=True)
print(f"Generated -- {decoded_text}")
这样,您可以确保xla_generate()
的输入始终接收与其跟踪时相同形状的输入,从而加快生成时间。您可以使用下面的代码进行验证:
import time
import tensorflow as tf
from transformers import AutoTokenizer, TFAutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("gpt2", padding_side="left", pad_token="</s>")
model = TFAutoModelForCausalLM.from_pretrained("gpt2")
xla_generate = tf.function(model.generate, jit_compile=True)
for input_string in ["TensorFlow is", "TensorFlow is a", "TFLite is a"]:
tokenized_input = tokenizer(input_string, pad_to_multiple_of=8, padding=True, return_tensors="tf")
start = time.time_ns()
generated_tokens = xla_generate(**tokenized_input, num_beams=2)
end = time.time_ns()
print(f"Execution time -- {(end - start) / 1e6:.1f} ms\n")
在 Tesla T4 GPU 上,您可以期望输出如下:
Execution time -- 30819.6 ms
Execution time -- 79.0 ms
Execution time -- 78.9 ms
第一次调用xla_generate()
由于跟踪而耗时,但后续调用速度快得多。请记住,任何时候对生成选项进行更改都会触发重新跟踪,从而导致生成时间变慢。
我们没有在本文档中涵盖🤗 Transformers 提供的所有文本生成选项。我们鼓励您阅读高级用例的文档。
额外资源
在这里,如果您想深入了解🤗 Transformers 中的 XLA 和一般情况下的 XLA,我们为您提供了一些额外资源。
-
这个 Colab 笔记本提供了一个交互式演示,如果您想要尝试 XLA 兼容的编码器-解码器(如T5)和仅解码器(如GPT2)文本生成模型。
-
这篇博客文章提供了 XLA 兼容模型的比较基准概述,以及对 TensorFlow 中 XLA 的友好介绍。
-
这篇博客文章讨论了我们在🤗 Transformers 中为 TensorFlow 模型添加 XLA 支持的设计理念。
-
学习更多关于 XLA 和 TensorFlow 图的推荐帖子:
使用 torch.compile() 优化推理
huggingface.co/docs/transformers/v4.37.2/en/perf_torch_compile
本指南旨在提供有关在🤗 Transformers 中使用 torch.compile()
为计算机视觉模型引入的推理加速的基准测试信息。
torch.compile 的好处
根据模型和 GPU,torch.compile()
在推理过程中可以提高高达 30%的速度。要使用 torch.compile()
,只需安装任何版本高于 2.0 的torch
。
编译模型需要时间,因此如果您只编译模型一次而不是每次推理时都编译,这将非常有用。要编译您选择的任何计算机视觉模型,请按照以下示例在模型上调用 torch.compile()
:
from transformers import AutoModelForImageClassification
model = AutoModelForImageClassification.from_pretrained(MODEL_ID).to("cuda")
+ model = torch.compile(model)
compile()
有多种编译模式,它们在编译时间和推理开销方面有所不同。max-autotune
比 reduce-overhead
花费更长的时间,但推理速度更快。默认模式在编译速度上最快,但与 reduce-overhead
相比在推理时间上效率不高。在本指南中,我们使用了默认模式。您可以在这里了解更多信息。
我们对torch.compile
在不同的计算机视觉模型、任务、硬件类型和批处理大小上进行了基准测试,使用的是torch
版本 2.0.1。
基准测试代码
下面您可以找到每个任务的基准测试代码。我们在推理之前对 GPU 进行预热,并使用相同的图像进行 300 次推理的平均时间。
使用 ViT 进行图像分类
import torch
from PIL import Image
import requests
import numpy as np
from transformers import AutoImageProcessor, AutoModelForImageClassification
url = 'http://images.cocodataset.org/val2017/000000039769.jpg'
image = Image.open(requests.get(url, stream=True).raw)
processor = AutoImageProcessor.from_pretrained("google/vit-base-patch16-224")
model = AutoModelForImageClassification.from_pretrained("google/vit-base-patch16-224").to("cuda")
model = torch.compile(model)
processed_input = processor(image, return_tensors='pt').to(device="cuda")
with torch.no_grad():
_ = model(**processed_input)
使用 DETR 进行目标检测
from transformers import AutoImageProcessor, AutoModelForObjectDetection
processor = AutoImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = AutoModelForObjectDetection.from_pretrained("facebook/detr-resnet-50").to("cuda")
model = torch.compile(model)
texts = ["a photo of a cat", "a photo of a dog"]
inputs = processor(text=texts, images=image, return_tensors="pt").to("cuda")
with torch.no_grad():
_ = model(**inputs)
使用 Segformer 进行图像分割
from transformers import SegformerImageProcessor, SegformerForSemanticSegmentation
processor = SegformerImageProcessor.from_pretrained("nvidia/segformer-b0-finetuned-ade-512-512")
model = SegformerForSemanticSegmentation.from_pretrained("nvidia/segformer-b0-finetuned-ade-512-512").to("cuda")
model = torch.compile(model)
seg_inputs = processor(images=image, return_tensors="pt").to("cuda")
with torch.no_grad():
_ = model(**seg_inputs)
下面您可以找到我们进行基准测试的模型列表。
图像分类
图像分割
目标检测
下面您可以找到使用和不使用 torch.compile()
的推理持续时间的可视化,以及每个模型在不同硬件和批处理大小下的百分比改进。
下面您可以找到每个模型使用和不使用 compile()
的推理持续时间(毫秒)。请注意,OwlViT 在较大批处理大小时会导致 OOM。
A100(批处理大小:1)
任务/模型 | torch 2.0 - 无编译 | torch 2.0 - 编译 |
---|---|---|
图像分类/ViT | 9.325 | 7.584 |
图像分割/Segformer | 11.759 | 10.500 |
目标检测/OwlViT | 24.978 | 18.420 |
图像分类/BeiT | 11.282 | 8.448 |
目标检测/DETR | 34.619 | 19.040 |
图像分类/ConvNeXT | 10.410 | 10.208 |
图像分类/ResNet | 6.531 | 4.124 |
图像分割/Mask2former | 60.188 | 49.117 |
图像分割/Maskformer | 75.764 | 59.487 |
图像分割/MobileNet | 8.583 | 3.974 |
目标检测/Resnet-101 | 36.276 | 18.197 |
目标检测/Conditional-DETR | 31.219 | 17.993 |
A100(批量大小:4)
任务/模型 | torch 2.0 - 无编译 | torch 2.0 - 编译 |
---|---|---|
图像分类/ViT | 14.832 | 14.499 |
图像分割/Segformer | 18.838 | 16.476 |
图像分类/BeiT | 13.205 | 13.048 |
目标检测/DETR | 48.657 | 32.418 |
图像分类/ConvNeXT | 22.940 | 21.631 |
图像分类/ResNet | 6.657 | 4.268 |
图像分割/Mask2former | 74.277 | 61.781 |
图像分割/Maskformer | 180.700 | 159.116 |
图像分割/MobileNet | 14.174 | 8.515 |
目标检测/Resnet-101 | 68.101 | 44.998 |
目标检测/Conditional-DETR | 56.470 | 35.552 |
A100(批量大小:16)
任务/模型 | torch 2.0 - 无编译 | torch 2.0 - 编译 |
---|---|---|
图像分类/ViT | 40.944 | 40.010 |
图像分割/Segformer | 37.005 | 31.144 |
图像分类/BeiT | 41.854 | 41.048 |
目标检测/DETR | 164.382 | 161.902 |
图像分类/ConvNeXT | 82.258 | 75.561 |
图像分类/ResNet | 7.018 | 5.024 |
图像分割/Mask2former | 178.945 | 154.814 |
图像分割/Maskformer | 638.570 | 579.826 |
图像分割/MobileNet | 51.693 | 30.310 |
目标检测/Resnet-101 | 232.887 | 155.021 |
目标检测/Conditional-DETR | 180.491 | 124.032 |
V100(批量大小:1)
任务/模型 | torch 2.0 - 无编译 | torch 2.0 - 编译 |
---|---|---|
图像分类/ViT | 10.495 | 6.00 |
图像分割/Segformer | 13.321 | 5.862 |
目标检测/OwlViT | 25.769 | 22.395 |
图像分类/BeiT | 11.347 | 7.234 |
目标检测/DETR | 33.951 | 19.388 |
图像分类/ConvNeXT | 11.623 | 10.412 |
图像分类/ResNet | 6.484 | 3.820 |
图像分割/Mask2former | 64.640 | 49.873 |
图像分割/Maskformer | 95.532 | 72.207 |
图像分割/MobileNet | 9.217 | 4.753 |
目标检测/Resnet-101 | 52.818 | 28.367 |
目标检测/Conditional-DETR | 39.512 | 20.816 |
V100(批量大小:4)
任务/模型 | torch 2.0 - 无编译 | torch 2.0 - 编译 |
---|---|---|
图像分类/ViT | 15.181 | 14.501 |
图像分割/Segformer | 16.787 | 16.188 |
图像分类/BeiT | 15.171 | 14.753 |
目标检测/DETR | 88.529 | 64.195 |
图像分类/ConvNeXT | 29.574 | 27.085 |
图像分类/ResNet | 6.109 | 4.731 |
图像分割/Mask2former | 90.402 | 76.926 |
图像分割/Maskformer | 234.261 | 205.456 |
图像分割/MobileNet | 24.623 | 14.816 |
目标检测/Resnet-101 | 134.672 | 101.304 |
目标检测/Conditional-DETR | 97.464 | 69.739 |
V100(批量大小:16)
任务/模型 | torch 2.0 - 无编译 | torch 2.0 - 编译 |
---|---|---|
图像分类/ViT | 52.209 | 51.633 |
图像分割/Segformer | 61.013 | 55.499 |
图像分类/BeiT | 53.938 | 53.581 |
目标检测/DETR | OOM | OOM |
图像分类/ConvNeXT | 109.682 | 100.771 |
图像分类/ResNet | 14.857 | 12.089 |
图像分割/Mask2former | 249.605 | 222.801 |
图像分割/Maskformer | 831.142 | 743.645 |
图像分割/MobileNet | 93.129 | 55.365 |
目标检测/Resnet-101 | 482.425 | 361.843 |
目标检测/Conditional-DETR | 344.661 | 255.298 |
T4(批量大小:1)
任务/模型 | torch 2.0 - 无编译 | torch 2.0 - 编译 |
---|---|---|
图像分类/ViT | 16.520 | 15.786 |
图像分割/Segformer | 16.116 | 14.205 |
目标检测/OwlViT | 53.634 | 51.105 |
图像分类/BeiT | 16.464 | 15.710 |
目标检测/DETR | 73.100 | 53.99 |
图像分类/ConvNeXT | 32.932 | 30.845 |
图像分类/ResNet | 6.031 | 4.321 |
图像分割/Mask2former | 79.192 | 66.815 |
图像分割/Maskformer | 200.026 | 188.268 |
图像分割/MobileNet | 18.908 | 11.997 |
目标检测/Resnet-101 | 106.622 | 82.566 |
目标检测/Conditional-DETR | 77.594 | 56.984 |
T4(批量大小:4)
任务/模型 | torch 2.0 - 无编译 | torch 2.0 - 编译 |
---|---|---|
图像分类/ViT | 43.653 | 43.626 |
图像分割/Segformer | 45.327 | 42.445 |
图像分类/BeiT | 52.007 | 51.354 |
目标检测/DETR | 277.850 | 268.003 |
图像分类/ConvNeXT | 119.259 | 105.580 |
图像分类/ResNet | 13.039 | 11.388 |
图像分割/Mask2former | 201.540 | 184.670 |
图像分割/Maskformer | 764.052 | 711.280 |
图像分割/MobileNet | 74.289 | 48.677 |
目标检测/Resnet-101 | 421.859 | 357.614 |
目标检测/Conditional-DETR | 289.002 | 226.945 |
T4(批量大小:16)
任务/模型 | torch 2.0 - 无编译 | torch 2.0 - 编译 |
---|---|---|
图像分类/ViT | 163.914 | 160.907 |
图像分割/Segformer | 192.412 | 163.620 |
图像分类/BeiT | 188.978 | 187.976 |
目标检测/DETR | OOM | OOM |
图像分类/ConvNeXT | 422.886 | 388.078 |
图像分类/ResNet | 44.114 | 37.604 |
图像分割/Mask2former | 756.337 | 695.291 |
图像分割/Maskformer | 2842.940 | 2656.88 |
图像分割/MobileNet | 299.003 | 201.942 |
目标检测/Resnet-101 | 1619.505 | 1262.758 |
目标检测/Conditional-DETR | 1137.513 | 897.390 |
PyTorch Nightly
我们还在 PyTorch nightly(2.1.0dev,可以在这里找到)上进行了基准测试,并观察到未编译和编译模型的延迟均有所改善。
A100
任务/模型 | 批量大小 | torch 2.0 - 无编译 | torch 2.0 - 编译 |
---|---|---|---|
图像分类/BeiT | 未分批 | 12.462 | 6.954 |
图像分类/BeiT | 4 | 14.109 | 12.851 |
图像分类/BeiT | 16 | 42.179 | 42.147 |
目标检测/DETR | 未分批 | 30.484 | 15.221 |
目标检测/DETR | 4 | 46.816 | 30.942 |
目标检测/DETR | 16 | 163.749 | 163.706 |
T4
任务/模型 | 批量大小 | torch 2.0 - 无编译 | torch 2.0 - 编译 |
---|---|---|---|
图像分类/BeiT | 未分批 | 14.408 | 14.052 |
图像分类/BeiT | 4 | 47.381 | 46.604 |
图像分类/BeiT | 16 | 42.179 | 42.147 |
目标检测/DETR | 未分批 | 68.382 | 53.481 |
目标检测/DETR | 4 | 269.615 | 204.785 |
目标检测/DETR | 16 | OOM | OOM |
V100
任务/模型 | 批量大小 | torch 2.0 - 无编译 | torch 2.0 - 编译 |
---|---|---|---|
图像分类/BeiT | 未分批 | 13.477 | 7.926 |
图像分类/BeiT | 4 | 15.103 | 14.378 |
图像分类/BeiT | 16 | 52.517 | 51.691 |
目标检测/DETR | 未分批 | 28.706 | 19.077 |
目标检测/DETR | 4 | 88.402 | 62.949 |
目标检测/DETR | 16 | OOM | OOM |
减少开销
我们在夜间对 A100 和 T4 进行了reduce-overhead
编译模式的基准测试。
A100
任务/模型 | 批量大小 | torch 2.0 - 无编译 | torch 2.0 - 编译 |
---|---|---|---|
图像分类/ConvNeXT | 未分批 | 11.758 | 7.335 |
图像分类/ConvNeXT | 4 | 23.171 | 21.490 |
图像分类/ResNet | 未分批 | 7.435 | 3.801 |
图像分类/ResNet | 4 | 7.261 | 2.187 |
目标检测/条件 DETR | 未批处理 | 32.823 | 11.627 |
目标检测/条件 DETR | 4 | 50.622 | 33.831 |
图像分割/MobileNet | 未批处理 | 9.869 | 4.244 |
图像分割/MobileNet | 4 | 14.385 | 7.946 |
T4
任务/模型 | 批处理大小 | torch 2.0 - 无编译 | torch 2.0 - 编译 |
---|---|---|---|
图像分类/ConvNeXT | 未批处理 | 32.137 | 31.84 |
图像分类/ConvNeXT | 4 | 120.944 | 110.209 |
图像分类/ResNet | 未批处理 | 9.761 | 7.698 |
图像分类/ResNet | 4 | 15.215 | 13.871 |
目标检测/条件 DETR | 未批处理 | 72.150 | 57.660 |
目标检测/条件 DETR | 4 | 301.494 | 247.543 |
图像分割/MobileNet | 未批处理 | 22.266 | 19.339 |
图像分割/MobileNet | 4 | 78.311 | 50.983 |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库