在这里插入图片描述

近期公司有大模型项目落地。在前期沟通时,对于算力估算和采购方案许多小伙伴不太了解,在此对相关的算力估算和选择进行一些总结。

不喜欢过程的可以直接 跳到HF上提供的模型计算器

要估算大模型的所需的显卡算力,首先要了解大模型的参数基础知识。

大模型的规模、参数的理解


模型参数单位

我们的项目中客户之前测试过Qwen1.5 -110B的模型,效果还比较满意。(Qwen还是国产模型中比较稳定的也是很多项目的首选)
模型中的110B 术语通常指的是大型神经网络模型的参数数量。其中的 “B” 代表 “billion”,也就是十亿。表示模型中的参数量,每个参数用来存储模型的权重和偏差等信息。110B也就是1100亿参数。(大模型的能力涌现基本都是参数要在千亿之上,几十亿几百亿的参数模型虽然也能满足大多数场景,但是谁不想要个更好的呢?『手动狗头』)
比如最新的Qwen2 开源了 5种模型规模,包括0.5B、1.5B、7B、57B-A14B和72B;(57B-A14B模型是570亿参数激活140亿的意思 )

模型参数精度

在深度学习领域内,构建高效率且精确度高的神经网络模型时,选择适当的参数精度至关重要。参数的精度通常指的是其存储和计算方式所采用的数据类型(data type),这直接关系到内存使用、计算性能以及最终模型的准确性。

在这里插入图片描述

单精度浮点数(float32)

单精度浮点数主要用于表示实数,具有较高的数值精确度,广泛应用于深度学习任务中。它的优点是能提供足够的精度来处理大部分的计算需求,而其32位的数据结构在内存中的占用空间仅为4字节。

半精度浮点数(float16)

相较于单精度浮点数,半精度浮点数具有较低的存储位数(16位),因此可以显著减少所需内存,并加速计算过程。这种数据类型尤其适合在图形处理器(GPU)上进行大量并行处理的应用场景。

BF16,全称为Brain Floating Point Format,是一种16位的半精度浮点数格式,特别为机器学习和人工智能领域的高性能计算优化而设计。BF16是在FP32(单精度浮点数)的基础上进行简化,旨在通过减少存储和计算需求来加速计算密集型任务,同时尽量减少对模型精度的损失。
具体来说,BF16浮点数格式由以下几个部分组成:

  • 1位符号位,用来表示数值的正负。
  • 8位指数位,相较于FP16的5位指数位,这提供了更宽的数值范围,有助于避免在处理较大或较小数值时的上溢或下溢问题。
  • 7位尾数位(也称为小数部分或 mantissa),相比FP16的10位尾数位,这导致BF16在表示小数时的精度略低。

BF16的设计目标是在牺牲一定精度的前提下,提供足够的数值范围来支持深度学习模型的高效运行,尤其是在大规模分布式训练和高性能推理场景中。由于许多深度学习算法对数值的精确度要求不是极高,因此这种折衷在很多情况下是可以接受的,并且能够显著减少内存带宽需求和提高计算效率。

值得注意的是,BF16最初由Google提出,并在一些特定的硬件平台上获得了支持,比如某些CPU(特别是支持ARM NEON指令集的处理器)和NVIDIA的Ampere架构及后续版本的GPU,这些硬件直接支持BF16的加速运算,进一步促进了BF16在AI应用中的普及。

双精度浮点数(float64)

提供更高的数值精确度的是双精度浮点数,通常用于对数值精确度要求较高的任务中,如某些科学研究或金融分析等。虽然提供了额外的准确性保障,但这种数据类型占用内存较大,每单位存储需要8字节。

整数(int32, int64)

在深度学习中,对于处理离散值的情况,例如类别标签,通常会使用整型数据。有符号整数(如int32)能表示正负值,而无符号整数(如uint32)仅用于非负整数。这两种类型分别需要占用4字节和8字节的内存。

参数精度的选择是深度学习实践中的一门艺术,它要求平衡对精度的需求、系统资源限制以及计算效率之间的考量。通常情况下,在不影响模型性能的前提下,倾向于使用较低精度的数据类型以节省内存并提高计算速度。然而,当面对需要更高精度分析的任务时,可能需要权衡增加的内存消耗与提升的准确度。
理解各种参数精度的特点及其在深度学习中的应用是构建高效、优化资源利用和提高模型性能的关键因素之一。在实际应用中,选择适当的参数精度应当基于任务的具体需求、硬件能力以及预期的计算资源限制综合考虑。

大模型的文件体积

大模型除了参数大以外,体积也是相当的大。在了解了参数精度后,我们也就可以一句参数规模推算大模型的体积了。

以Qwen1.5-110B 来说

全精度模型参数是float32类型, 占用4个字节,粗略计算:1b(10亿)个模型参数,约占用4G存储实际大小计算公式:10^9 * 4 / 1024^3 ~= 3.725 GB

那么Qwen1.5-110B的参数量为110B,那么加载模型参数需要的显存为:3.725 * 110 ~= 409.75GB

我们可以看下HF上Qwen1.5-110B的开源文件(该开源文件使用的是BF16位数,所以按照计算公式,体积大小为200G左右):

国内在HF上开源的大模型一般都是提供半精度(FP16或BF16)

在这里插入图片描述

大模型不光参数大,体积也巨大,要运行这个规模的模型,需要十分高的硬件配置,带来了很大的难度,成为阻碍人工智能发展的障碍,于是很多脑子好使的研究人员就提出了一系列的压缩技术,比如下面说到的常用的量化技术。

大模型量化技术

近年来,在深度学习领域中,研究人员探索使用低比特整数表示模型参数以实现模型的压缩与加速。量化(quantization)技术是其中的关键方法。

量化技术与int4、int8

  • 量化技术:通过将浮点数映射到较低位数的整数来减小模型在计算和存储时的需求。
  • int4: 使用4位二进制表示一个整数,存储模型参数。量化过程会将浮点数转换为可表示在有限范围内的整数值,并用4个比特记录这些值。
  • int8: 类似于int4,但使用8位二进制表示整数,提供更大的表示范围和精确度。

内存占用

  • int4:不直接以字节单位描述位数,通常通过位操作存储数据。
  • int8:占用1个字节(即8位)空间。

注意事项

量化会导致信息损失,因此需要在压缩效果与模型性能之间进行权衡。根据具体任务需求选择合适的量化精度是关键步骤。通过许多评测综合来看到的结果,通常选择8位(int8)或更低的位宽来表示权重和激活值,但也可根据实际需求选择其他位宽,如BF16。

许多小伙伴在模型本地化尝试中会使用Ollama 来进行本地化部署,有人会觉得本地化部署后,Ollama加载的模型回答质量和能力有些下降。这是因为Ollama 致力于 实现 本地化部署大模型,限于本地化部署用户许多没有足够的算力,在通过ollama run xxxx 模型时,Ollama 会默认下载经过int4 量化后的模型,这些模型已经进行了量化压缩,期精度已经产生了较大的衰减所致。
Ollama也可以加载更好的从HF上下载的模型,具体的可以参看Ollama的官方文档。此处不在赘述。

大模型推理资源测算


项目落地中,大模型进行推理是核心应用,模型推理是利用预先训练好的神经网络对新数据进行预测或分类的过程。这一阶段相对节省资源,但其显存需求仍由多个关键因素决定。

影响显存的因素

模型结构的显存影响

  • 深度与复杂性:较深、更复杂的模型(包含更多层和神经元)在推理过程中会产生大量中间计算结果,因此需要更多显存用于存储这些信息。

输入数据尺寸的影响

  • 大型或高分辨率的输入数据会显著增加所需的显存量。这是因为更大尺寸的数据需要更多的内存来处理和临时缓存。

批处理大小(BatchSize)的重要性

  • 批量处理:更大的批次意味着一次同时处理更多样本,从而增加了显存在存储所有样本及中间结果时的压力。

数据类型的选择

  • 采用较低精度的数据类型(如半精度浮点数而非全精度),可以显著减少所需的内存容量。这通过降低数据表示的复杂性和占用空间来实现。

中间计算的影响

  • 额外计算负载:推理过程中产生的中间结果,需要额外的内存来存储这些计算产物,特别是在模型结构包含大量运算或高数据依赖性的层时尤为明显。

显存的估算方式

要估算模型推理时所需的显存,可以按照以下步骤:

  1. 模型加载: 计算模型中所有参数的大小,包括权重和偏差。
  2. 确定输入数据尺寸: 根据模型结构和输入数据大小,计算推理过程中每个中间计算结果的大小。
  3. 选择批次大小: 考虑批处理大小和数据类型对显存的影响。
  4. 计算显存大小: 将模型参数大小、中间计算结果大小和额外内存需求相加,以得出总显存需求或者使用合适的库或工具计算出推理过程中所需的显存。

通常情况下,现代深度学习框架(如TensorFlow、PyTorch等)提供了用于推理的工具和函数,可以帮助您估算和管理模型推理时的显存需求。

比如我们前面提到的Qwen1.5-110B 模型

全精度 FP 32 情况下 占 4字节,1B 参数量 需要 10^9 * 4 / 1024^3 ~= 3.725 GB 显存,110B 的话 就是 3.725 * 110 ~= 409.75 显存。
HF上开源的是BF16 ,粗略计算 需要:110(模型量) * 10^9 (1B) * 2 (BF16占2字节存储) / 1024^3 (换算成GB) ~= 204 GB 显存

简化一下 ,模型推理所需基本显存

  • 全精度模型就是 xxB * 4
  • 半精度模型就是 xxB * 2
  • 量化8 模型就是 xxB * 1
  • 量化4 模型就是 xxB / 2

常规情况下,模型精度影响模型效果,在模型推理情况下 量化8模型虽有精度损失,但仍然较小,大部分大模型推理场景下效果都还不错,int4 模型在本地部署辅助个人工作中较为常用(自己用就忍一忍,毕竟免费的)。

注意: 上述是加载模型到显存所需大小,在模型的推理过程中,可能会产生一些中间计算结果,这些中间结果也会占用一定的显存,所以显存大小不能刚好是参数量的大小,不然就会OOM。

关于显卡的选择

国外企业项目

  • 老黄的NVIDIA A800/A100/V100/H100 国外企业项目,看资金和业务情况选择(基本都是选顶级,多张卡加起来显存足够就可以)

国内企业项目

  • 能采购到国内存量NVIDIA显卡的优先最顶级80G显存卡 (国内能采购到的肯定是不差钱的,就选最顶配就好)
  • 不能采购NVIDIA 显卡的,可以采用云部署的,可以在几大云平台租用NVIDIA 显卡算力。
  • 私有化落地无法采购NVIDIA显卡的,可以选择国产算力显卡(沐曦C500、华为910B都可以)。

但是国产算力显卡 目前没有80G显存类型可选,沐曦C500、华为910B 算力显卡 最高都是64G ,两者的GPU浮点算力都是足够的,采购情况主要就是看显存和软件支持生态。
目前华为卡所需适配开发略有难度,不是高度战略合作不建议选(有问题协调技术配合难度较大)。
沐曦适配开发难度尚可,需要厂家支持力度较小(因为兼容CUDA)。(部分项目已经成功上线)

比如上文提到的Qwen1.5-110B 使用int8 进行量化的模型 ,推理场景跑起来 一般会选则 4张沐曦C500.(可能你会问60G的模型,不是两卡就能跑么?首先,跑起来和顺畅能用不是一个概念,另一个是项目落地不是给一个人用的,要考虑并发和多用户,真实落地采购时需要依据项目情况来进行测算。两张卡是能跑起来,但是落实到业务场景中就不够了。选择4卡也是依据项目情况,目前国内许多小项目的应用场景内部都是尝试型,所以业务量比较小,如果是ToC场景,业务量比较大,就要依据业务情况进行评估测算。)

大模型训练资源测算


绝大部分私有化部署项目都是因为集团数据资产不能外流,所以模型训练微调必须在内部进行私有数据训练。大模型要训练,那么所需的算力资源也是要配备的。

模型训练过程中,目标是通过优化算法调整模型的参数,使其能够更好地适应给定的数据集,并具备良好的泛化能力。这一过程通常对显存需求较高,主要因为涉及以下关键因素:

影响训练的关键因素

模型权重

  • 定义与作用:模型权重是神经网络中连接强度的表示,决定了输入特征与各层之间的传递方式。
  • 占用内存:权重通常以高维矩阵的形式存储,大小由模型架构决定。

梯度

  • 计算过程:在训练阶段,通过反向传播算法计算出损失函数对每个参数的偏导数(即梯度),用于指导参数更新。
  • 占用内存:梯度与权重具有相同的维度,其大小取决于模型结构和数据集。

优化器参数

  • 目的:优化器根据梯度调整参数以加速收敛过程。常用的优化器包括SGD、AdamW等。
  • 额外存储
    • SGD:占用与权重相同量的内存,用于计算梯度和更新参数。
    • AdamW:每个参数需要8字节的存储空间并维护2个状态(一阶和二阶矩估计),因此相对于权重而言,占用约2倍内存。经过优化后的AdamW版本,如使用bitsandbytes库,将每个参数的存储减至2字节。
    • 更新规则:这些状态信息在每次迭代时用于计算梯度方向和步长。

所需显存的计算方式

所需显存的大小 = 模型参数占用 + 梯度占用 + 优化器占用 + CUDA kernel占用

以Qwen1.5-110B BF16 为例

  • 模型参数:等于参数量每个参数所需内存, 204GB 内存
  • 梯度:同上,等于参数量每个梯度参数所需内存,204GB内存。
  • 优化器参数:不同的优化器所储存的参数量不同。对于常用的 AdamW 来说,需要储存两倍的模型参数(用来储存一阶和二阶momentum),也就是408GB内存
  • 除此之外,CUDA kernel也会占据一些 RAM,大概 1.3GB 左右

样本数量:训练样本依据模型不同,训练方式不同和规模不同而不同,可以依据算力情况增减任务。

所以BF16 精度下 训练大约需要 800+ GB的显存,8卡A100-80G看来是不太够的…… 如果是全精度FP32 精度的 大约就是1.6T(可参见后文HF 的测算工具结果)。显存的计算可以按照上述部分进行相加计算。注意,这只是基础要求部分,在实际应用中,需要根据模型结构、数据批次大小、优化算法等因素来估计和管理显存的使用,以防止内存不足导致训练过程中断。使用一些工具和库(如TensorFlow、PyTorch等)可以帮助您监控和管理显存的使用情况。实际影响显存占用的因素还有很多,这只是算力选型中的一个粗略估算。

我们的项目中并未选择千亿大模型(算力太贵了……),而是选择了Qwen2 中的小规模参数模型进行 业务验证。

简化一下 ,模型训练所需基本显存

  • 全精度模型就是 xxB * 16
  • 半精度模型就是 xxB * 8
  • 量化8 模型(量化后进行推理可以,但不建议进行训练)
  • 量化4 模型(量化后进行推理可以,但不建议进行训练)

关于使用INT8量化模型进行微调(fine-tuning)或无监督训练,这通常是不直接推荐或支持的。主要原因如下:

  1. 精度损失: INT8量化可能会引入一定的精度损失,这意味着模型的权重和激活值不再是原始的高精度浮点数表示,这可能影响模型进一步学习的能力,尤其是在微调时,模型需要有能力在现有基础上做出细粒度的调整。
  2. 梯度计算: 训练过程依赖于精确的梯度计算来进行权重更新,而INT8量化可能会导致梯度信息的损失,影响训练效果。
  3. 硬件与软件支持: 大部分训练框架和硬件加速器(GPU、TPU等)在训练时主要支持FP32或FP16等更高精度格式,对INT8的支持主要集中在推理阶段。
  4. 工具链限制: 当前的量化工具和库大多设计用于推理优化,而非训练阶段。例如,TensorRT的INT8量化主要用于优化推理速度,而不是训练流程。

备注:模型训练所需GPU显存是本地笔记本所不能完成的,但是我们一般正常使用模型的预测推理服务还是没多大问题的,一个比较经济的做法是在数据不涉密的情况下,租用云算力进行测试和验证。

HF上提供的模型计算器


模型计算器
在这里插入图片描述

不同训练微调所需显存参考

按照上文介绍及模型所需推理,依据模型和所采用的精度来总结,模型训练基础所需显存理论是8倍显存,但是实际中训练可能需要10倍左右显存(其他因素影响,比如训练样本的多少、优化技术等情况),以下是网络上整理的采用不同技术进行训练微调所需显存的一个参考,作为补充。

方法 bits 7B 13B 30B 65B 8*7B
全参数微调 16 160GB 320GB 600GB 1200GB 900GB
Freeze 16 20GB 40GB 120GB 240GB 200GB
LoRA 16 16GB 32GB 80GB 160GB 120GB
QLoRA 8 10GB 16GB 40GB 80GB 80GB
QLoRA 4 6GB 12GB 24GB 48GB 32GB

其他脚本工具


脚本来自互联网

大模型输入长度与显存的关系

# 当前程序从启动到目前 查看显存最大占用情况:torch.cuda.max_memory_allocated("cuda:0")/1024**2
# 以chatglm2为例:
from transformers import AutoModel, AutoTokenizer
import torch
tokenizer = AutoTokenizer.from_pretrained("../chatglm2-6b", trust_remote_code=True)
model = AutoModel.from_pretrained("../chatglm2-6b", trust_remote_code=True).half().to(torch.device("cuda:0"))
model = model.eval()
res=[]
for i in range(100,6000,200):
    prompt=text[:i]
    max_length = len(prompt)
    top_p = 1
    temperature = 0.8
    response, history=model.chat(tokenizer, prompt, [], max_length=max_length, top_p=top_p,
                                               temperature=temperature)
    print(torch.cuda.max_memory_allocated("cuda:0")/1024**2)      #默认返回当前程序从开始所占用的最大显存
    print("=============================")
    res.append(torch.cuda.max_memory_allocated("cuda:0")/1024**2)
#查看显存
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"]=["SimHei"]
x=[i for i in range(100,6000,200)]
#服务器无法绘图,进行拷贝查看y=res
y=[11981.630859375, 12013.05419921875, 12056.77099609375, 12104.171875, 12147.87890625, 12194.2587890625, 12246.16259765625, 12283.880859375, 12326.7431640625, 12372.0830078125, 12418.00439453125, 12463.4248046875, 12504.59521484375, 12553.66162109375, 12593.26708984375, 12638.40234375, 12682.5390625, 12728.97021484375, 12790.80615234375, 12846.7744140625, 12894.125, 12944.5283203125, 12990.2294921875, 13030.0068359375, 13080.41064453125, 13126.8134765625, 13173.08203125, 13218.35302734375, 13265.4736328125, 13305.5966796875]
plt.plot(x,y,c="r")
plt.ylabel("显存占用:M")
plt.xlabel("序列长度:token")
plt.show()

大模型推理 多线程与显存的关系

print(torch.cuda.memory_allocated("cuda:0")/1024**2)     #torch 模型占用显存
print(torch.cuda.max_memory_allocated("cuda:0")/1024**2)  #torch 模型占用显存最大值
显存大小=torch.cuda.memory_allocated("cuda:0")/1024**2 + (torch.cuda.max_memory_allocated("cuda:0")/1024**2)* 线程数

文章内容为学习笔记,如有不当,欢迎指正交流。

更多推荐