中国保密在线培训网站,电商平台排行榜前十名,李继红跪舔坊网站建设,百度通用网址文章目录 一、内存估算1.1 Gradio Demos1.2 The Command 二、使用Accelerate加载超大模型2.1 模型加载的常规流程2.2 加载空模型2.3 分片检查点#xff08;Sharded checkpoints#xff09;2.4 示例#xff1a;使用Accelerate推理GPT2-1.5B2.5 device_map 三、bitsandbytes量… 文章目录 一、内存估算1.1 Gradio Demos1.2 The Command 二、使用Accelerate加载超大模型2.1 模型加载的常规流程2.2 加载空模型2.3 分片检查点Sharded checkpoints2.4 示例使用Accelerate推理GPT2-1.5B2.5 device_map 三、bitsandbytes量化3.1 环境依赖3.2 量化示例3.3 保存和加载 8 位模型3.4 微调量化模型 四、分布式推理4.1 使用torch.distributed进行分布式推理4.2 使用Accelerate进行分布式推理 《探索多种方案下 LLM 的预训练性能》 一、内存估算 参考《Understanding how big of a model can fit on your machine》、《Transformer 估算》 目前使用大模型一个非常困难的方面是了解当前显卡的内存可以容纳多大的模型例如将模型加载到 CUDA 上。为了帮助解决这个问题Accelerate提供了accelerate estimate-memory 命令行界面来进行估算。
1.1 Gradio Demos
打开Gradio Demos页面。输入Model Name or URL就可以进行估算。 该计算器将告诉您纯粹加载模型而不是执行推理需要多少内存模型所需的最小内存建议 表示为“最大层”的大小模型的训练大约是其大小的 4 倍对于 Adam在执行推理时预计会额外增加 20%。未来将进行更多测试以获得每个模型更准确的基准。目前此工具支持使用 transformers 和 timm 托管的所有模型此计算的精确度在实际值的百分之几内例如当以全精度加载到 CUDA 上时加载 bert-base-cased 实际上需要 413.68 MB 计算器估计是 413.18 MB 。
1.2 The Command 你也可以使用命令行界面来获取结果例如计算bert-base-cased 的内存占用量
accelerate estimate-memory bert-base-cased这将下载 bert-based-cased 的 config.json文件 在 meta 设备上加载模型并报告它将使用多少空间
dtypeLargest LayerTotal SizeTraining using Adamfloat3284.95 MB418.18 MB1.61 GBfloat1642.47 MB206.59 MB826.36 MBint821.24 MB103.29 MB413.18 MBint410.62 MB51.65 MB206.59 MB
默认情况下它将返回所有支持的数据类型 int4 到 float32 你也可以进行过滤
accelerate estimate-memory bert-base-cased --dtypes float32 float16dtypeLargest LayerTotal SizeTraining using Adamfloat3284.95 MB413.18 MB1.61 GBfloat1642.47 MB206.59 MB826.36 MB
如果无法确定来自哪个库可以传入库的名称
HuggingFaceM4/idefics-80b-instruct
accelerate estimate-memory HuggingFaceM4/idefics-80b-instruct --library_name transformersdtypeLargest LayerTotal SizeTraining using Adamfloat323.02 GB297.12 GB1.16 TBfloat161.51 GB148.56 GB594.24 GBint8772.52 MB74.28 GB297.12 GBint4386.26 MB37.14 GB148.56 GB
timm/resnet50.a1_in1k
accelerate estimate-memory timm/resnet50.a1_in1k --library_name timmdtypeLargest LayerTotal SizeTraining using Adamfloat329.0 MB97.7 MB390.78 MBfloat164.5 MB48.85 MB195.39 MBint82.25 MB24.42 MB97.7 MBint41.12 MB12.21 MB48.85 MB
二、使用Accelerate加载超大模型 本章参考 《Handling big models for inference》、《Distributed Inference with Accelerate》《使用HuggingFace的Accelerate库加载和运行超大模型》有关本章用到的各种API和更多超大模型加载内容可参考《Working with large models》 2.1 模型加载的常规流程
在 PyTorch 中加载预训练模型时通常的工作流程如下所示
import torchmy_model ModelClass(...)
state_dict torch.load(checkpoint_file)
my_model.load_state_dict(state_dict)简而言之这些步骤是
使用随机初始化的权重创建模型从磁盘加载模型权重通常称为state_dict将这些权重加载到模型中将模型加载到相应设备上比如GPU进行模型推理
如果模型不是很大的话使用上面的代码加载权重是可以的。但当我们处理大型模型时此工作流程有一些明显的局限性。
在第一步中需要在RAM中加载一个完整版本的模型并花费一些时间对权重进行随机初始化在第三步将被丢弃。在第二步中需要再次在RAM中加载一个完整版本的模型其中包含预训练的权重。 对于包含60亿参数的模型这意味每一步都需要需要24GB的RAMfloat32每个占四字节总共需要48GB其中一半用于在FP16中加载模型。如果加载更大的模型比如BLOOM或OPT-176B1760亿个参数需要大概1.4TB的内存空间。这样内存肯定是不够的因此我们需要做一些技巧性的改变。
2.2 加载空模型 PyTorch 1.9版本提供了一个叫做meta的新设备。使用该设备可以创建非常大的张量而无需考虑内存或显存的大小。同样的也可以方便创建模型而无需加载权重。例如下面的代码如果在Colab上或kaggle的kernel运行会报错OOM因为默认使用精度为32位的情况下创建下述张量需要40G内存。
import torch
large_tensor torch.randn(100000, 100000)而如果与meta设备来创建即可只定义张量形状而不消耗内存。
import torch
large_tensor torch.randn(100000, 100000, devicemeta)
large_tensor # 张量只有形状并没有数据不占据内存或显存。
tensor(..., devicemeta, size(100000, 100000))Accelerate 引入的第一个帮助处理大型模型的工具是上下文管理器 init_empty_weights()可以使用meta初始化一个空模型只有shape没有数据。
from accelerate import init_empty_weightswith init_empty_weights():model nn.Sequential(*[nn.Linear(10000, 10000) for _ in range(1000)])这可以初始化一个略大于100亿参数的空模型在init_empty_weights()下的初始化过程中每当创建一个参数它会立即被移到meta设备上。定义好上述模型后可以喂输入然后得到一个meta设备的输出张量同样只有形状没有数据其实就是进行了shape计算。
2.3 分片检查点Sharded checkpoints 当模型过大无法将其整体加载到内存中时仍可通过分片方式加载尤其在有一个或多个GPU的情况下因为GPU提供了更多内存。 检查点分片将检查点分割成多个较小的文件称为检查点分片。Accelerate库能够处理这种检查点分片前提是遵循特定的格式
检查点应该存储在一个文件夹中。文件夹中包含多个具有部分状态字典的文件每个文件对应模型的不同部分。有一个JSON格式的索引文件该文件包含一个字典将参数名称映射到包含其权重的文件。
使用 save_model() 可以轻松地将模型分片
accelerator.wait_for_everyone() # 确保所有进程训练完成
accelerator.save_model(model, save_directory, max_shard_size1GB, safe_serializationTrue)得到的结果类似于
first_state_dict.bin
index.json
second_state_dict.bin其中index.json是以下文件
{linear1.weight: first_state_dict.bin,linear1.bias: first_state_dict.bin,linear2.weight: second_state_dict.bin,linear2.bias: second_state_dict.bin
}加载时你可以使用 load_checkpoint_in_model() 函数将其加载在特定设备上。
load_checkpoint_in_model(unwrapped_model, save_directory, device_map{: device})也可以使用load_checkpoint_and_dispatch() 函数在空模型中加载完整检查点或分片检查点它还会自动在您可用的设备GPU、CPU RAM上分配这些权重。完整的模型分片推理过程见此YouTube视频。 load_checkpoint_and_dispatch函数常用参数为model、checkpoint、device_map、max_memory、no_split_module_classes后两个参数将在后面讲到。 2.4 示例使用Accelerate推理GPT2-1.5B
使用 minGPT库的默认配置初始化模型
git clone https://github.com/karpathy/minGPT.git
pip install minGPT/from accelerate import init_empty_weights
from mingpt.model import GPTmodel_config GPT.get_default_config()
model_config.model_type gpt2-xl
model_config.vocab_size 50257
model_config.block_size 1024with init_empty_weights():model GPT(model_config)下载模型权重并加载
pip install huggingface_hubfrom huggingface_hub import snapshot_download
from accelerate import load_checkpoint_and_dispatchcheckpoint marcsun13/gpt2-xl-linear-sharded
weights_location snapshot_download(repo_idcheckpoint)
model load_checkpoint_and_dispatch(model, checkpointweights_location, device_mapauto, no_split_module_classes[Block]
)上述代码中
使用load_checkpoint_and_dispatch()加载检查点到空模型。设置device_mapauto会让Accelerate自动判断把模型的每个层放在哪里: 优先尽量放在GPU上如果GPU空间不够将剩余层放在CPU RAM上如果CPU RAM也不够将剩余层存储在硬盘上以memory-mapped tensors的形式。 通过no_split_module_classes参数可以指定某些层不被分割比如包含残差连接的Block等模块。 memory-mapped tensors简称 mmap tensors是PyTorch提供的一种特殊的tensors,它允许将数据存储在磁盘文件中而不占用宝贵的RAM内存CPU可以直接对磁盘文件中的数据进行读写操作就像操作RAM中的tensors一样。mmap tensors既可以享受性能接近RAM的缓存系统带来的读写速度同时也不会耗尽宝贵的RAM空间从而使Accelerate支持超大模型的训练 推理 现在我们的模型位于多个设备上也许还有在硬盘上的部分但它仍然可以用作常规 PyTorch 模型进行推理
from mingpt.bpe import BPETokenizer
tokenizer BPETokenizer()
inputs tokenizer(Hello, my name is).to(0)outputs model.generate(x1, max_new_tokens10, do_sampleFalse)[0]
tokenizer.decode(outputs.cpu().squeeze())在幕后 Accelerate 添加了钩子hook到模型以便
在每一层输入被放在正确的设备上因此即使您的模型分布在多个GPU上它也能正常工作。对于卸载到CPU上的权重在前向传播之前将它们放在GPU上然后在之后清理对于卸载到硬盘上的权重在前向传播之前将其加载到RAM中然后放在GPU上之后再清理 这样即使您的模型在GPU或CPU上装不下也可以进行推理同时需要注意的是Accelerate 通过 hook 仅支持推理而不支持训练这是因为
内存限制在训练过程中,我们需要在每一层保留激活值来计算梯度,这会大大增加内存需求。但在推理时我们可以在每一层后立即释放激活,所以内存需求较小。计算复杂性训练需要频繁的反向传播权重需要频繁更新。反向传播涉及到复杂的计算图跟踪这在分布式环境下变得非常困难特别是当layer分布在不同设备时,实现反向传播会变得极具挑战性高效的分布式权重更新也非常困难。精度要求训练中需要精确计算梯度的值但在这种分布式环境下很难保证最终会影响模型的训练质量。相比之下在推理过程中不需要计算梯度。 当我们谈到深度学习中的 “hook” 时我们指的是一种机制允许在神经网络的不同部分插入自定义代码以便在训练或推断的过程中执行一些额外的操作。这些操作可能包括记录中间激活值、梯度、权重等或者进行某种修改以适应特定需求。 hook一般包括Forward Hook前向钩子和Backward Hook反向钩子。上面的讲解中hooks 的作用是确保输入被正确放置在合适的设备上。下面是一段关于hook的伪代码 def forward_hook(module, input, output):# 在前向传播中执行的自定义操作
def backward_hook(module, grad_input, grad_output):# 在反向传播中执行的自定义操作# 注册前向和反向 hook
hook_handle model.fc.register_forward_hook(forward_hook)
hook_handle_backward model.fc.register_backward_hook(backward_hook)# 运行前向传播和反向传播
output model(input_data)
output.backward(torch.randn_like(output))# 移除 hook
hook_handle.remove()
hook_handle_backward.remove()2.5 device_map 查看和设置device_map 您可以通过访问模型的 hf_device_map 属性来查看 Accelerate 选择的 device_map一个字典包含模型模块、权重以及对应的设备 比如对于上述GPT2-1.5B模型 model.hf_device_map{transformer.wte: 0,transformer.wpe: 0,transformer.drop: 0,transformer.h.0: 0,...transformer.h.21: 0, transformer.h.22: 1, transformer.h.23: 1, transformer.h.24: 1,...transformer.h.47: 1, transformer.ln_f: 1, lm_head: 1}你也可以自定义设备映射指定要使用的 GPU 设备数字、 “cpu” 或 “disk” 并将其传入 device_map {transformer.wte: cpu,transformer.wpe: 0,transformer.drop: cpu,transformer.h.0: disk
}model load_checkpoint_and_dispatch(model, checkpointweights_location, device_mapdevice_map)device_map选项 device_map有四个可选参数。当GPU不足以容纳整个模型时所有选项都会产生相同的结果即优先加载在GPU上而后分别是 CPU和磁盘。当 GPU显存大于模型大小时每个选项会有如下差异 auto 或 balanced:Accelerate将会根据所有GPU均衡切分权重尽量均匀的切分到各个GPU上balanced_low_0:在第一个GPU上序号为0会尽量节省显存其它个GPU均匀分割权重。这种模式可以有效节省第一个GPU的显存用于模型生成等计算操作generate函数sequentialAccelerate按照GPU的顺序占用显存因此排序靠后的GPU显存占用会少一些。 auto 和 balanced 目前会产生相同的结果但如果我们找到更有意义的策略 auto 的行为将来可能会发生变化而 balanced 将保持不变。 accelerate.infer_auto_device_map此函数用于为给定的模型生成设备映射优先使用 GPU然后是 CPU最后是硬盘。因为所有计算都是通过分析模型参数的大小和数据类型来完成的所以可以是meta上的空模型。以下是具体参数 modeltorch.nn.Module要分析的模型。max_memory可选设备标识符到最大内存的字典。如果未设置将默认为可用的最大内存。no_split_module_classes可选不应在设备之间分割的层类名称列表例如任何具有残差连接的层。dtype可选如果提供加载权重时将其转换为该类型。例如from accelerate import infer_auto_device_map, init_empty_weights
from transformers import AutoConfig, AutoModelForCausalLM
config AutoConfig.from_pretrained(facebook/opt-13b)
with init_empty_weights():model AutoModelForCausalLM.from_config(config)
device_map infer_auto_device_map(model, no_split_module_classes[OPTDecoderLayer], dtypefloat16)special_dtypes可选如果提供考虑某些特定权重的特殊数据类型将覆盖作为所有权重默认值的 dtype。verbose可选默认为 False是否在函数构建设备映射时提供调试语句。 max_memory 参数 您可以使用 max_memory 参数来限制每个 GPU 和CPU上使用的内存赋予GPU应该传递标识符例如 0,1内存值可以是整数以字节为单位也可以是表示数字及其单位的字符串例如 “10GiB” 或 “10GB” 。 需要注意的是当 PyTorch 中发生第一次分配时它会加载 CUDA 内核该内核大约需要 1-2GB 内存具体取决于 GPU。因此可用内存总是小于 GPU 的实际大小。要查看实际使用了多少内存请执行 torch.ones(1).cuda() 并查看内存使用情况。 示例一GPU 内存不超过10GiBCPU内存不超过30GiB from accelerate import infer_auto_device_mapdevice_map infer_auto_device_map(my_model, max_memory{0: 10GiB, 1: 10GiB, cpu: 30GiB})对于一些生成任务的模型如果想您有许多 GPU且想使用更大的batch size进行推理第一个GPU应该分配较少的内存。例如在 8x80 A100 上使用 BLOOM-176B接近理想的分配为 max_memory {0: 30GIB, 1: 46GIB, 2: 46GIB, 3: 46GIB, 4: 46GIB, 5: 46GIB, 6: 46GIB, 7: 46GIB}三、bitsandbytes量化 参考《QuantizationAccelerate》、《大规模 Transformer 模型 8 比特矩阵乘简介 - 基于 Hugging Face Transformers、Accelerate 以及 bitsandbytes》 Accelerate库集成了bitsandbytes 量化功能几行代码就可以实现4位量化和8位量化。要了解有关 bitsandbytes 量化工作原理的更多信息请查看8 位量化和 4 位量化的博客文章。transformers中也集成了bitsandbytes 量化功能相关内容可查看量化文档或者我的另一篇博客《Hugging Face高性能技术五Transformer高效推断bitsandbytes、FlashAttention、 BetterTransformer》
3.1 环境依赖
使用前安装相关依赖
pip install bitsandbytes
pip install githttps://github.com/huggingface/accelerate.git3.2 量化示例
安装 minGPT 和 huggingface_hub 以运行示例
git clone https://github.com/karpathy/minGPT.git
pip install minGPT/
pip install huggingface_hub从 minGPT 库中获取 GPT2 模型配置然后使用 init_empty_weights().初始化一个空模型
from accelerate import init_empty_weights
from mingpt.model import GPTmodel_config GPT.get_default_config()
model_config.model_type gpt2-xl
model_config.vocab_size 50257
model_config.block_size 1024with init_empty_weights():empty_model GPT(model_config)需要获取模型权重的路径。该路径可以是 state_dict 文件例如“pytorch_model.bin”或包含分片检查点的文件夹。
from huggingface_hub import snapshot_download
weights_location snapshot_download(repo_idmarcsun13/gpt2-xl-linear-sharded)使用 BnbQuantizationConfig 设置量化配置
from accelerate.utils import BnbQuantizationConfig
# 8位量化
bnb_quantization_config BnbQuantizationConfig(load_in_8bitTrue, llm_int8_threshold 6)
# 4位量化
bnb_quantization_config BnbQuantizationConfig(load_in_4bitTrue, bnb_4bit_compute_dtypetorch.bfloat16, bnb_4bit_use_double_quantTrue, bnb_4bit_quant_typenf4)使用 load_and_quantize_model()量化所选配置的空模型
from accelerate.utils import load_and_quantize_model
quantized_model load_and_quantize_model(empty_model, weights_locationweights_location, bnb_quantization_configbnb_quantization_config, device_map auto)量化操作的具体实现都集成在bitsandbytes 库的Linear8bitLt 模块中它是torch.nn.modules 的子类。与 nn.Linear 模块略有不同其参数属于 bnb.nn.Int8Params 类而不是 nn.Parameter 类。然后我们会调用replace_8bit_linear函数将所有 nn.Linear 模块替换为 bitsandbytes.nn.Linear8bitLt模块。
def replace_8bit_linear(model, threshold6.0, module_to_not_convertlm_head):for name, module in model.named_children():if len(list(module.children())) 0:replace_8bit_linear(module, threshold, module_to_not_convert)if isinstance(module, nn.Linear) and name ! module_to_not_convert:with init_empty_weights():model._modules[name] bnb.nn.Linear8bitLt(module.in_features,module.out_features,module.bias is not None,has_fp16_weightsFalse,thresholdthreshold,)return model此函数递归地将 meta 设备上初始化的给定模型的所有 nn.Linear 层替换为 Linear8bitLt 模块。这里必须将 has_fp16_weights 属性设置为 False以便直接将权重加载为 Int8并同时加载其量化统计信息。另外我们放弃了对某些模块 (这里是 lm_head) 进行替换因为我们希望保持输出层的原始精度以获得更精确、更稳定的结果。也就是说 bitsandbytes只会量化transformer结构中除首层之外的全连接层。
3.3 保存和加载 8 位模型
使用accelerate.save_model保存8位模型至于 4 位模型序列化目前还不支持。
from accelerate import Accelerator
accelerate Accelerator()
new_weights_location path/to/save_directory
accelerate.save_model(quantized_model, new_weights_location)quantized_model_from_saved load_and_quantize_model(empty_model, weights_locationnew_weights_location, bnb_quantization_configbnb_quantization_config, device_map auto)如果 GPU 不足以存储整个模型您可以通过传递自定义 device_map 将某些模块卸载到 cpu/磁盘。对于 8 位量化所选模块将转换为 8 位精度。以下是一个示例
device_map {transformer.wte: 0,transformer.wpe: 0,transformer.drop: 0,transformer.h: cpu,transformer.ln_f: disk,lm_head: disk,
}完整量化代码可查看colab notebook示例《Accelerate quantization.ipynb》示例中将GPT2模型量化为4位模型和8位模型并进行推理。
3.4 微调量化模型 参考《Quantize Transformers models》 8 位或 4 位量化模型无法执行全量训练。但是您可以利用参数高效微调方法 (PEFT) 来微调这些模型详见peft Github示例
Colab notebook《Finetune-opt-bnb-peft.ipynb》使用peft库的LoRa功能微调量化后的OPT-6.7b模型《Finetuning Whisper-large-V2 on Colab using PEFT-Lora BNB INT8 training Streaming dataset》《Finetuning Whisper-large-V2 on Colab using PEFT-Lora BNB INT8 training》 请注意device_mapauto 仅用于推理。这是因为推理过程通常不需要进行梯度计算而且模型参数已经在训练期间被优化因此在推理时可以更灵活地选择设备。 加载模型进行训练时不需要显式传递device_map参数。系统会自动将模型加载到GPU上进行训练。如果需要您可以将设备映射设置为特定设备例如cuda:0, 0, torch.device(cuda:0)。
四、分布式推理 参考《Distributed Inference with Accelerate》 4.1 使用torch.distributed进行分布式推理 分布式推理是一种常见的用例尤其是自然语言处理 (NLP) 模型。用户通常希望发送多个不同的提示每个提示发送到不同的 GPU然后返回结果。下面是一个普通示例
import torch
import torch.distributed as dist
from diffusers import DiffusionPipelinepipe DiffusionPipeline.from_pretrained(runwayml/stable-diffusion-v1-5, torch_dtypetorch.float16)使用使用torch.distributed模块进行分布式推理
def run_inference(rank, world_size):dist.init_process_group(nccl, rankrank, world_sizeworld_size)pipe.to(rank)if torch.distributed.get_rank() 0:prompt a dogelif torch.distributed.get_rank() 1:prompt a catresult pipe(prompt).images[0]result.save(fresult_{rank}.png)可以看到我们需要根据进程的rank选择不同的提示进行推理这种方式需要手动管理每个进程的提示显得有些繁琐。
4.2 使用Accelerate进行分布式推理 通过 Accelerate我们可以通过使用Accelerator.split_between_processes()上下文管理器也存在于PartialState和AcceleratorState中来简化这个过程。此函数会自动将您传递给它的任何数据无论是提示一组张量先前数据的字典等在所有进程之间进行分割可能进行填充以便您立即使用。让我们使用上下文管理器重写上面的示例
from accelerate import PartialState # Can also be Accelerator or AcceleratorState
from diffusers import DiffusionPipelinepipe DiffusionPipeline.from_pretrained(runwayml/stable-diffusion-v1-5, torch_dtypetorch.float16)
distributed_state PartialState()
pipe.to(distributed_state.device)# Assume two processes
with distributed_state.split_between_processes([a dog, a cat]) as prompt:result pipe(prompt).images[0]result.save(fresult_{distributed_state.process_index}.png)然后使用accelerate launch启动代码已生成配置文件
accelerate launch distributed_inference.py使用的特定配置文件启动
accelerate launch --config_file my_config.json distributed_inference.py不使用配置文件进行启动会受到一些警告可以执行 accelerate config default来解决
accelerate launch --num_processes 2 distributed_inference.py现在我们不需要写分布式推理的样板代码了Accelerate会自动分配数据到各个GPU上。如果碰到数据分割不均的情况比如我们有 3 个提示但只有 2 个 GPU。在上下文管理器下第一个 GPU 将接收前两个提示第二个 GPU 将接收第三个提示确保所有提示都被拆分并且不需要任何开销。 如果需要对所有 GPU 的结果执行一些操作例如聚合它们并执行一些后处理可以在 split_between_processes 中传递 apply_paddingTrue以确保提示列表被填充到相同的长度多余的数据从最后一个样本中获取。这样所有 GPU 将具有相同数量的提示然后可以收集结果。
from accelerate import PartialState # Can also be Accelerator or AcceleratorState
from diffusers import DiffusionPipelinepipe DiffusionPipeline.from_pretrained(runwayml/stable-diffusion-v1-5, torch_dtypetorch.float16)
distributed_state PartialState()
pipe.to(distributed_state.device)# Assume two processes
with distributed_state.split_between_processes([a dog, a cat, a chicken], apply_paddingTrue) as prompt:result pipe(prompt).images此时第一个 GPU提示列表五 [“a dog”, “a cat”]第二个 GPU提示列表为 [“a chicken”, “a chicken”]。