Transformers 4.37 中文文档(十九)(7)https://developer.aliyun.com/article/1564928
ZeRO-3 和 Infinity 细微差别
ZeRO-3 与 ZeRO-2 非常不同,因为它具有参数分片功能。
ZeRO-Infinity 进一步扩展了 ZeRO-3,以支持 NVMe 内存和多项其他速度和可伸缩性改进。
尽管我们已经尽力使事情能够正常工作,而无需对您的模型进行任何特殊更改,但在某些情况下,您可能会发现需要以下信息。
构建大型模型
DeepSpeed/ZeRO-3 可以处理具有数万亿参数的模型,这些参数可能无法适应现有的 RAM。在这种情况下,但也如果您希望初始化速度更快,请使用*deepspeed.zero.Init()*上下文管理器(也是函数装饰器)初始化模型,如下所示:
from transformers import T5ForConditionalGeneration, T5Config import deepspeed with deepspeed.zero.Init(): config = T5Config.from_pretrained("t5-small") model = T5ForConditionalGeneration(config)
正如您所看到的,这为您提供了一个随机初始化的模型。
如果要使用预训练模型,只要is_deepspeed_zero3_enabled()
返回True
,model_class.from_pretrained
将激活此功能,当前情况下,这是由 TrainingArguments 对象设置的,如果传递的 DeepSpeed 配置文件包含 ZeRO-3 配置部分。因此,您必须在调用from_pretrained
之前创建 TrainingArguments 对象之前。以下是可能的顺序示例:
from transformers import AutoModel, Trainer, TrainingArguments training_args = TrainingArguments(..., deepspeed=ds_config) model = AutoModel.from_pretrained("t5-small") trainer = Trainer(model=model, args=training_args, ...)
如果您正在使用官方示例脚本,并且您的命令行参数包括--deepspeed ds_config.json
并启用了 ZeRO-3 配置,则一切都已经为您完成,因为示例脚本是这样编写的。
注意:如果模型的 fp16 权重无法适应单个 GPU 的内存,则必须使用此功能。
有关此方法和其他相关功能的详细信息,请参阅构建大型模型。
此外,当加载 fp16 预训练模型时,您将希望告诉from_pretrained
使用torch_dtype=torch.float16
。有关详细信息,请参见 from_pretrained-torch-dtype。
收集参数
在多个 GPU 上的 ZeRO-3 中,除了当前执行层的参数外,没有单个 GPU 拥有所有参数。因此,如果您需要一次访问所有层的所有参数,有一种特定的方法可以做到。您很可能不需要它,但如果需要,请参阅收集参数。
然而,我们在几个地方内部使用它,一个例子是在from_pretrained
中加载预训练模型权重时。我们一次加载一层,然后立即将其分区到所有参与的 GPU 上,因为对于非常大的模型,将其加载到一个 GPU 上然后分散到多个 GPU 上是不可能的,由于内存限制。
此外,在 ZeRO-3 下,如果您编写自己的代码并遇到看起来像模型参数权重的问题:
tensor([1.0], device="cuda:0", dtype=torch.float16, requires_grad=True)
强调tensor([1.])
,或者如果出现错误,指出参数大小为1
,而不是某个更大的多维形状,这意味着参数被分区,您看到的是 ZeRO-3 占位符。
ZeRO 推理
ZeRO 推理使用与 ZeRO-3 训练相同的配置。您只需要不需要优化器和调度程序部分。实际上,如果要与训练共享相同的配置文件,可以将这些部分保留在配置文件中。它们将被忽略。
否则,您只需要传递通常的 TrainingArguments 参数。例如:
deepspeed --num_gpus=2 your_program.py <normal cl args> --do_eval --deepspeed ds_config.json
唯一重要的是您需要使用 ZeRO-3 配置,因为 ZeRO-2 对推理没有任何好处,因为只有 ZeRO-3 执行参数分片,而 ZeRO-1 执行梯度和优化器状态的分片。
以下是在使用所有可用 GPU 部署 DeepSpeed 时运行run_translation.py
的示例:
deepspeed examples/pytorch/translation/run_translation.py \ --deepspeed tests/deepspeed/ds_config_zero3.json \ --model_name_or_path t5-small --output_dir output_dir \ --do_eval --max_eval_samples 50 --warmup_steps 50 \ --max_source_length 128 --val_max_target_length 128 \ --overwrite_output_dir --per_device_eval_batch_size 4 \ --predict_with_generate --dataset_config "ro-en" --fp16 \ --source_lang en --target_lang ro --dataset_name wmt16 \ --source_prefix "translate English to Romanian: "
由于在推理中不需要额外大内存用于优化器状态和梯度,您应该能够在相同的硬件上适应更大的批次和/或序列长度。
此外,DeepSpeed 目前正在开发一个名为 Deepspeed-Inference 的相关产品,它与 ZeRO 技术没有关系,而是使用张量并行性来扩展无法适应单个 GPU 的模型。这是一个正在进行的工作,一旦该产品完成,我们将提供集成。
内存要求
由于 Deepspeed ZeRO 可以将内存卸载到 CPU(和 NVMe),该框架提供了一些实用程序,允许您根据使用的 GPU 数量告诉需要多少 CPU 和 GPU 内存。
让我们估计在单个 GPU 上对“bigscience/T0_3B”进行微调所需的内存:
$ python -c 'from transformers import AutoModel; \ from deepspeed.runtime.zero.stage3 import estimate_zero3_model_states_mem_needs_all_live; \ model = AutoModel.from_pretrained("bigscience/T0_3B"); \ estimate_zero3_model_states_mem_needs_all_live(model, num_gpus_per_node=1, num_nodes=1)' [...] Estimated memory needed for params, optim states and gradients for a: HW: Setup with 1 node, 1 GPU per node. SW: Model with 2783M total params, 65M largest layer params. per CPU | per GPU | Options 70.00GB | 0.25GB | offload_param=cpu , offload_optimizer=cpu , zero_init=1 70.00GB | 0.25GB | offload_param=cpu , offload_optimizer=cpu , zero_init=0 62.23GB | 5.43GB | offload_param=none, offload_optimizer=cpu , zero_init=1 62.23GB | 5.43GB | offload_param=none, offload_optimizer=cpu , zero_init=0 0.37GB | 46.91GB | offload_param=none, offload_optimizer=none, zero_init=1 15.56GB | 46.91GB | offload_param=none, offload_optimizer=none, zero_init=0
因此,您可以将其放在单个 80GB GPU 上,不使用 CPU 卸载,或者使用一个小型的 8GB GPU,但是需要大约 60GB 的 CPU 内存。请记住,这只是参数、优化器状态和梯度的内存 - 您将需要更多内存用于 cuda 内核、激活和临时存储。
然后就是成本与速度的权衡。购买/租用较小的 GPU(或较少的 GPU,因为您可以使用 Deepspeed ZeRO 来使用多个 GPU)。但这样会更慢,所以即使您不关心某件事情会多快完成,减速也会直接影响使用 GPU 的持续时间,从而增加成本。因此,请进行实验并比较哪种方法最好。
如果您有足够的 GPU 内存,请确保禁用 CPU/NVMe 卸载,因为这将使一切更快。
例如,让我们重复使用 2 个 GPU:
$ python -c 'from transformers import AutoModel; \ from deepspeed.runtime.zero.stage3 import estimate_zero3_model_states_mem_needs_all_live; \ model = AutoModel.from_pretrained("bigscience/T0_3B"); \ estimate_zero3_model_states_mem_needs_all_live(model, num_gpus_per_node=2, num_nodes=1)' [...] Estimated memory needed for params, optim states and gradients for a: HW: Setup with 1 node, 2 GPUs per node. SW: Model with 2783M total params, 65M largest layer params. per CPU | per GPU | Options 70.00GB | 0.25GB | offload_param=cpu , offload_optimizer=cpu , zero_init=1 70.00GB | 0.25GB | offload_param=cpu , offload_optimizer=cpu , zero_init=0 62.23GB | 2.84GB | offload_param=none, offload_optimizer=cpu , zero_init=1 62.23GB | 2.84GB | offload_param=none, offload_optimizer=cpu , zero_init=0 0.74GB | 23.58GB | offload_param=none, offload_optimizer=none, zero_init=1 31.11GB | 23.58GB | offload_param=none, offload_optimizer=none, zero_init=0
因此,您可能需要 2 个 32GB 或更高内存的 GPU,而不需要将内存卸载到 CPU。
有关完整信息,请参阅memory estimators。
提交问题
以下是如何提交问题,以便我们可以快速找到问题的根源并帮助您解除工作阻塞。
在您的报告中,请始终包括:
- 在报告中提供完整的 Deepspeed 配置文件
- 如果您使用的是 Trainer 的命令行参数,或者如果您自己编写了 Trainer 设置,则使用 TrainingArguments 参数。请不要转储 TrainingArguments,因为它有数十个与问题无关的条目。
- 输出:
python -c 'import torch; print(f"torch: {torch.__version__}")' python -c 'import transformers; print(f"transformers: {transformers.__version__}")' python -c 'import deepspeed; print(f"deepspeed: {deepspeed.__version__}")'
- 如果可能的话,请包含一个链接到一个 Google Colab 笔记本,我们可以用它来重现问题。您可以使用这个notebook作为起点。
- 除非不可能,请始终使用我们可以使用的标准数据集,而不是自定义数据集。
- 如果可能,请尝试使用现有的examples之一来重现问题。
需要考虑的事项:
- Deepspeed 通常不是问题的原因。
一些提交的问题被证明与 Deepspeed 无关。也就是说,一旦从设置中移除了 Deepspeed,问题仍然存在。
因此,如果不是绝对明显是 Deepspeed 相关的问题,例如您可以看到有异常并且可以看到涉及 Deepspeed 模块,首先在没有 Deepspeed 的设置中重新测试您的设置。只有在问题仍然存在时才提到 Deepspeed 并提供所有必要的细节。 - 如果您明确知道问题出在 DeepSpeed 核心而不是集成部分,请直接向Deepspeed 提交问题。如果您不确定,请不要担心,任何一个问题跟踪器都可以,我们会在您发布后找出问题,并在需要时将您重定向到另一个问题跟踪器。
故障排除
深度速度进程在启动时被终止,没有回溯
如果deepspeed
进程在启动时被终止,没有回溯,通常意味着程序尝试分配比您的系统具有的 CPU 内存更多的内存,或者您的进程被允许分配的内存,而操作系统内核终止了该进程。这是因为您的配置文件很可能已经配置了offload_optimizer
或offload_param
或两者都配置为转移到cpu
。如果您有 NVMe,尝试将其转移到 NVMe,如果您正在运行 ZeRO-3。这是如何估算特定模型所需内存量的方法。
训练和/或评估/预测损失为 NaN
当一个以 bf16 混合精度模式预训练的模型尝试在 fp16 下使用时,通常会发生这种情况(无论是否使用混合精度)。大多数在 TPU 上训练的模型,通常是由 Google 发布的模型都属于这一类(例如,几乎所有基于 t5 的模型)。在这种情况下,解决方案是要么使用 fp32,要么使用 bf16,如果您的硬件支持的话(TPU、Ampere GPU 或更新)。
另一个问题可能与使用 fp16 有关。当您配置此部分时:
{ "fp16": { "enabled": "auto", "loss_scale": 0, "loss_scale_window": 1000, "initial_scale_power": 16, "hysteresis": 2, "min_loss_scale": 1 } }
并且您在日志中看到 Deepspeed 报告OVERFLOW!
如下:
0%| | 0/189 [00:00<?, ?it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 262144, reducing to 262144 1%|▌ | 1/189 [00:00<01:26, 2.17it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 262144, reducing to 131072.0 1%|█▏ [...] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1 14%|████████████████▌ | 27/189 [00:14<01:13, 2.21it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1 15%|█████████████████▏ | 28/189 [00:14<01:13, 2.18it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1 15%|█████████████████▊ | 29/189 [00:15<01:13, 2.18it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1 [...]
这意味着 Deepspeed 损失缩放器无法找到一个缩放系数来克服损失溢出。
(此处的日志已经过处理,以便更易阅读。)
在这种情况下,通常需要提高initial_scale_power
的值。将其设置为"initial_scale_power": 32
通常会解决问题。
注
- 虽然 DeepSpeed 有一个可通过 pip 安装的 PyPI 软件包,但强烈建议从源安装,以最好地匹配您的硬件,并且如果您需要启用某些功能,比如 1 比特 Adam,在 pypi 分发中是不可用的。
- 您不必使用 Trainer 来使用🤗 Transformers 的 DeepSpeed - 您可以使用任何模型与您自己的训练器,并且您将根据DeepSpeed 集成说明来调整后者。
非 Trainer Deepspeed 集成
HfDeepSpeedConfig 用于将 Deepspeed 集成到🤗 Transformers 核心功能中,当未使用 Trainer 时。它唯一要做的就是处理 Deepspeed ZeRO-3 参数收集,并在from_pretrained
调用期间自动将模型分割到多个 GPU 上。其他所有事情都需要您自己来做。
使用 Trainer 时,一切都会自动处理。
当不使用 Trainer 时,为了有效部署 DeepSpeed ZeRO-3,您必须在实例化模型之前实例化 HfDeepSpeedConfig 对象,并保持该对象处于活动状态。
如果您正在使用 Deepspeed ZeRO-1 或 ZeRO-2,则根本不需要使用HfDeepSpeedConfig
。
例如,对于预训练模型:
from transformers.integrations import HfDeepSpeedConfig from transformers import AutoModel import deepspeed ds_config = {...} # deepspeed config object or path to the file # must run before instantiating the model to detect zero 3 dschf = HfDeepSpeedConfig(ds_config) # keep this object alive model = AutoModel.from_pretrained("gpt2") engine = deepspeed.initialize(model=model, config_params=ds_config, ...)
或对于非预训练模型:
from transformers.integrations import HfDeepSpeedConfig from transformers import AutoModel, AutoConfig import deepspeed ds_config = {...} # deepspeed config object or path to the file # must run before instantiating the model to detect zero 3 dschf = HfDeepSpeedConfig(ds_config) # keep this object alive config = AutoConfig.from_pretrained("gpt2") model = AutoModel.from_config(config) engine = deepspeed.initialize(model=model, config_params=ds_config, ...)
请注意,如果您没有使用 Trainer 集成,您将完全独立。基本上遵循Deepspeed网站上的文档。此外,您必须明确配置配置文件 - 不能使用"auto"
值,而必须使用实际值。
HfDeepSpeedConfig
class transformers.integrations.HfDeepSpeedConfig
( config_file_or_dict )
参数
config_file_or_dict
(Union[str, Dict]
)— DeepSpeed 配置文件或字典的路径。
此对象包含一个 DeepSpeed 配置字典,可以快速查询诸如零阶段之类的内容。
此对象的weakref
存储在模块的全局变量中,以便能够从 Trainer 对象不可用的区域访问配置(例如from_pretrained
和_get_resized_embeddings
)。因此,在程序仍在运行时,这个对象保持活动是很重要的。
Trainer 使用HfTrainerDeepSpeedConfig
子类。该子类具有将配置与 TrainingArguments 的值同步的逻辑,通过替换特殊占位符值:"auto"
。如果没有这种特殊逻辑,DeepSpeed 配置将不会以任何方式修改。
自定义 DeepSpeed ZeRO 推理
以下是一个示例,演示如何在无法将模型放入单个 GPU 的情况下进行 DeepSpeed ZeRO 推理,而不使用 Trainer。解决方案包括使用额外的 GPU 和/或将 GPU 内存转移到 CPU 内存。
这里需要理解的重要细微差别是,ZeRO 的设计方式使您可以并行处理不同 GPU 上的不同输入。
示例有大量注释并且是自我记录的。
确保:
- 如果您有足够的 GPU 内存,请禁用 CPU 卸载(因为它会减慢速度)
- 如果您拥有 Ampere 或更新的 GPU,请启用 bf16 以加快速度。如果您没有该硬件,可以启用 fp16,只要不使用在 bf16 混合精度(例如大多数 t5 模型)中预训练的模型。这些通常在 fp16 中溢出,您将看到垃圾输出。
#!/usr/bin/env python # This script demonstrates how to use Deepspeed ZeRO in an inference mode when one can't fit a model # into a single GPU # # 1\. Use 1 GPU with CPU offload # 2\. Or use multiple GPUs instead # # First you need to install deepspeed: pip install deepspeed # # Here we use a 3B "bigscience/T0_3B" model which needs about 15GB GPU RAM - so 1 largish or 2 # small GPUs can handle it. or 1 small GPU and a lot of CPU memory. # # To use a larger model like "bigscience/T0" which needs about 50GB, unless you have an 80GB GPU - # you will need 2-4 gpus. And then you can adapt the script to handle more gpus if you want to # process multiple inputs at once. # # The provided deepspeed config also activates CPU memory offloading, so chances are that if you # have a lot of available CPU memory and you don't mind a slowdown you should be able to load a # model that doesn't normally fit into a single GPU. If you have enough GPU memory the program will # run faster if you don't want offload to CPU - so disable that section then. # # To deploy on 1 gpu: # # deepspeed --num_gpus 1 t0.py # or: # python -m torch.distributed.run --nproc_per_node=1 t0.py # # To deploy on 2 gpus: # # deepspeed --num_gpus 2 t0.py # or: # python -m torch.distributed.run --nproc_per_node=2 t0.py from transformers import AutoTokenizer, AutoConfig, AutoModelForSeq2SeqLM from transformers.integrations import HfDeepSpeedConfig import deepspeed import os import torch os.environ["TOKENIZERS_PARALLELISM"] = "false" # To avoid warnings about parallelism in tokenizers # distributed setup local_rank = int(os.getenv("LOCAL_RANK", "0")) world_size = int(os.getenv("WORLD_SIZE", "1")) torch.cuda.set_device(local_rank) deepspeed.init_distributed() model_name = "bigscience/T0_3B" config = AutoConfig.from_pretrained(model_name) model_hidden_size = config.d_model # batch size has to be divisible by world_size, but can be bigger than world_size train_batch_size = 1 * world_size # ds_config notes # # - enable bf16 if you use Ampere or higher GPU - this will run in mixed precision and will be # faster. # # - for older GPUs you can enable fp16, but it'll only work for non-bf16 pretrained models - e.g. # all official t5 models are bf16-pretrained # # - set offload_param.device to "none" or completely remove the `offload_param` section if you don't # - want CPU offload # # - if using `offload_param` you can manually finetune stage3_param_persistence_threshold to control # - which params should remain on gpus - the larger the value the smaller the offload size # # For indepth info on Deepspeed config see # https://huggingface.co/docs/transformers/main/main_classes/deepspeed # keeping the same format as json for consistency, except it uses lower case for true/false # fmt: off ds_config = { "fp16": { "enabled": False }, "bf16": { "enabled": False }, "zero_optimization": { "stage": 3, "offload_param": { "device": "cpu", "pin_memory": True }, "overlap_comm": True, "contiguous_gradients": True, "reduce_bucket_size": model_hidden_size * model_hidden_size, "stage3_prefetch_bucket_size": 0.9 * model_hidden_size * model_hidden_size, "stage3_param_persistence_threshold": 10 * model_hidden_size }, "steps_per_print": 2000, "train_batch_size": train_batch_size, "train_micro_batch_size_per_gpu": 1, "wall_clock_breakdown": False } # fmt: on # next line instructs transformers to partition the model directly over multiple gpus using # deepspeed.zero.Init when model's `from_pretrained` method is called. # # **it has to be run before loading the model AutoModelForSeq2SeqLM.from_pretrained(model_name)** # # otherwise the model will first be loaded normally and only partitioned at forward time which is # less efficient and when there is little CPU RAM may fail dschf = HfDeepSpeedConfig(ds_config) # keep this object alive # now a model can be loaded. model = AutoModelForSeq2SeqLM.from_pretrained(model_name) # initialise Deepspeed ZeRO and store only the engine object ds_engine = deepspeed.initialize(model=model, config_params=ds_config)[0] ds_engine.module.eval() # inference # Deepspeed ZeRO can process unrelated inputs on each GPU. So for 2 gpus you process 2 inputs at once. # If you use more GPUs adjust for more. # And of course if you have just one input to process you then need to pass the same string to both gpus # If you use only one GPU, then you will have only rank 0. rank = torch.distributed.get_rank() if rank == 0: text_in = "Is this review positive or negative? Review: this is the best cast iron skillet you will ever buy" elif rank == 1: text_in = "Is this review positive or negative? Review: this is the worst restaurant ever" tokenizer = AutoTokenizer.from_pretrained(model_name) inputs = tokenizer.encode(text_in, return_tensors="pt").to(device=local_rank) with torch.no_grad(): outputs = ds_engine.module.generate(inputs, synced_gpus=True) text_out = tokenizer.decode(outputs[0], skip_special_tokens=True) print(f"rank{rank}:\n in={text_in}\n out={text_out}")
让我们将其保存为t0.py
并运行:
$ deepspeed --num_gpus 2 t0.py rank0: in=Is this review positive or negative? Review: this is the best cast iron skillet you will ever buy out=Positive rank1: in=Is this review positive or negative? Review: this is the worst restaurant ever out=negative
这是一个非常基本的示例,您将希望根据自己的需求进行调整。
生成细微差别
使用多个 GPU 与 ZeRO Stage-3 一起使用时,必须通过调用generate(..., synced_gpus=True)
来同步 GPU。如果不这样做,如果一个 GPU 在其他 GPU 之前完成生成,整个系统将挂起,因为其他 GPU 将无法接收停止生成的 GPU 的权重片段。
从transformers>=4.28
开始,如果未明确指定synced_gpus
,则在检测到这些条件时,它将自动设置为True
。但是,如果需要,仍然可以覆盖synced_gpus
的值。
测试 Deepspeed 集成
如果您提交涉及 DeepSpeed 集成的 PR,请注意我们的 CircleCI PR CI 设置没有 GPU,因此我们只在另一个 CI 每晚运行需要 GPU 的测试。因此,如果您在 PR 中收到绿色 CI 报告,这并不意味着 DeepSpeed 测试通过。
要运行 DeepSpeed 测试,请至少运行:
RUN_SLOW=1 pytest tests/deepspeed/test_deepspeed.py
如果更改了建模或 pytorch 示例代码中的任何内容,则还要运行模型动物园测试。以下将运行所有 DeepSpeed 测试:
RUN_SLOW=1 pytest tests/deepspeed
主 DeepSpeed 资源
论文:
最后,请记住,HuggingFace Trainer 只集成了 DeepSpeed,因此如果您在使用 DeepSpeed 方面遇到任何问题或疑问,请在 DeepSpeed GitHub 上提交问题。