首页 > 其他分享 >LLM的不同精度详解和显存占用,FP16,FP32,BF16

LLM的不同精度详解和显存占用,FP16,FP32,BF16

时间:2024-11-18 10:18:50浏览次数:3  
标签:显存 BF16 FP32 浮点数 torch num memory allocated float16

目录

前言

1、FP16

2、BF16

3、FP32

4、不同精度的显存占用

5、不同精度之间的转换

总结


前言

本文主要介绍LLM的三种不同精度FP16,FP32,BF16的概念和计算,并用pytorch进行演示;不同精度下的显存占用,以及不同精度的相互转换。


1、FP16

FP16也叫 float16,全称是Half-precision floating-point(半精度浮点数),在IEEE 754标准中是叫做binary16,简单来说是用16位二进制来表示的浮点数,如图:

一共有 16 位二进制,由三部分组成,其中:

  • Sign(符号位): 1 位,0表示整数;1表示负数。
  • Exponent(指数位):5位,表示整数部分,范围为00001(1)到11110(30),正常来说整数范围就是 2^12^{30} ,但为了指数位能够表示负数,在指数的基础上引入了一个偏置值,在二进制16位浮点数中,偏置值是 15,这个偏置值确保了指数位可以表示从-14到+15的范围即 2^{-14}2^{-15} ,而不是1到30,注:当指数位都为00000和11111时,它表示的是一种特殊情况,在IEEE 754标准中叫做非规范化情况,后面可以看到这种特殊情况怎么表示的。
  • Fraction(尾数位):10位,表示小数部分,存储的尾数位数为10位,但其隐含了首位的1,实际的尾数精度为11位,这里的隐含位可能有点难以理解,通俗来说,假设尾数部分为1001000000,为默认在其前面加一个1,最后变成1.1001000000然后换成10进制就是:
# 第一种计算方式
1.1001000000 = 1 * 2^0 + 1 * 2^(-1) + 0 * 2^(-2) + 0 * 2^(-3) + 1 * 2^(-4) + 0 * 2^(-5) + 0 * 2^(-6) + 0 * 2^(-7) + 0 * 2^(-8) + 0 * 2^(-9) + 0 * 2^(-10)= 1.5625
# 第二种计算方式
1.1001000000 = 1 + 576/1024 = 1.5625(576是1001000000变成10进制)

所以 fp16 转换成十进制有两种方式:

例如,FP16(float16)能表示最小的正数:

贴一个FP16(float16)特殊数值的情况:

接下来看一下在pytorch中是如何表示 fp16 的:

torch.finfo(torch.float16)
# 结果
finfo(resolution=0.001, min=-65504, max=65504, eps=0.000976562, smallest_normal=6.10352e-05, tiny=6.10352e-05, dtype=float16)

其中:

  1. resolution(分辨率):这个浮点数类型的在十进制上的分辨率,表示两个不同值之间的最小间隔。对于 torch.float16,分辨率是 0.001,就是说两个不同的 torch.float16 数值之间的最小间隔是 0.001。
  2. min(最小值):对于 torch.float16,最小值是 -65504。
  3. max(最大值):对于 torch.float16,最大值是 65504。
  4. eps(机器精度):机器精度表示在给定数据类型下,比 1 大的最小浮点数,对于 torch.float16,机器精度是 0.000976562,对应上表中的smallest number larger than one。
  5. smallest_normal(最小正规数):最小正规数是大于零的最小浮点数,对于 torch.float16,最小正规数是 6.10352e-05,对应上表中的smallest positive normal number
  6. tiny(最小非零数):最小非零数是大于零的最小浮点数,对于 torch.float16,最小非零数也是 6.10352e-05,也是对应上表中的smallest positive normal number

这里详细解释一下resolution(分辨率),看一个例子:

import torch

# 把10进制数转化为 torch.float16
num = 3.141
num_fp16 = torch.tensor(num).half()
print(num_fp16)
# 结果
tensor(3.1406, dtype=torch.float16)

num = 3.1415
num_fp16 = torch.tensor(num).half()
print(num_fp16)
# 结果
tensor(3.1406, dtype=torch.float16)
# 可以看到3.141和3.1415间隔只有0.0005,所以在float16下结果是一样的

num = 3.142
num_fp16 = torch.tensor(num).half()
print(num_fp16)
# 结果
tensor(3.1426, dtype=torch.float16)
# 可以看到结果不一样了

从上面代码可以看到,十进制中相隔0.001,在float16中才会有变化,这个时候会有一个疑问,难道精度只有小数点后三位?那怎么之前见了很多参数都是有很多小数点的?来看一下全过程,把float16变成2进制,再把2进制变成16进制:

import struct
def float16_to_bin(num):
    # 将float16数打包为2字节16位,使用struct.pack
    packed_num = struct.pack('e', num)

    # 解包打包后的字节以获取整数表示
    int_value = struct.unpack('H', packed_num)[0]

    # 将整数表示转换为二进制
    binary_representation = bin(int_value)[2:].zfill(16)
    return binary_representation

num = 3.141
num_fp16 = torch.tensor(num).half()
print(num_fp16)
binary_representation = float16_to_bin(num_fp16)
print(binary_representation)  # 打印二进制表示
# 结果
tensor(3.1406, dtype=torch.float16)
0100001001001000


num = 3.1415
num_fp16 = torch.tensor(num).half()
binary_representation = float16_to_bin(num_fp16)
print(binary_representation)  # 打印二进制表示
# 结果
tensor(3.1406, dtype=torch.float16)
0100001001001000  # 还是一样的结果

num = 3.142
num_fp16 = torch.tensor(num).half()
print(num_fp16)
binary_representation = float16_to_bin(num_fp16)
print(binary_representation)  # 打印二进制表示
# 结果
tensor(3.1426, dtype=torch.float16)
0100001001001001  # 不一样了

 再看一下把2进制变成16进制:

def binary_to_float16(binary_string):
    # 检查输入是否是有效的16位二进制字符串
    if len(binary_string) != 16:
        raise ValueError("输入的二进制字符串必须是16位长")

    # 提取组成部分:符号、指数、尾数
    sign = int(binary_string[0])  # 符号位
    exponent = int(binary_string[1:6], 2)  # 指数位
    mantissa = int(binary_string[6:], 2) / 1024.0  # 尾数位,除以2的10次方(即1024)以获得10位精度

    # 根据符号、指数和尾数计算float16值
    value = (-1) ** sign * (1 + mantissa) * 2 ** (exponent - 15)
    return value

# 10进制3.141对应float16:3.1406
binary_representation = "0100001001001000"
# 将二进制表示转换为float16
float16_value = binary_to_float16(binary_representation)
print("通过2进制转化后Float16值:", float16_value)
# 结果:
通过2进制转化后Float16值: 3.140625

# 10进制3.1415对应float16:3.1406
binary_representation = "0100001001001000"
# 将二进制表示转换为float16
float16_value = binary_to_float16(binary_representation)
print("通过2进制转化后Float16值:", float16_value)
# 结果:
通过2进制转化后Float16值: 3.140625

# 10进制3.142对应float16:3.1426
binary_representation = "0100001001001001"
# 将二进制表示转换为float16
float16_value = binary_to_float16(binary_representation)
print("通过2进制转化后Float16值:", float16_value)
# 结果:
通过2进制转化后Float16值: 3.142578125

因为在计算机中 float16值 是以2进制存储计算的,而 float16 所能表示的最小值为 2^{-10}=0.0009765625,近似等于0.001,所以当10进制数的变化超过 0.001 时 float16 值才有变化。注:在-1~1之间精度是0.0001,因为有隐含位1的关系,大家可以试一下。

2、BF16

BF16也叫做bfloat16(这是最常叫法),全称brain floating point,也是用16位二进制来表示的,和FP16不一样的地方就是指数位和尾数位不一样:

其中:

  • Sign(符号位): 1 位,0表示整数;1表示负数
  • Exponent(指数位):8位,表示整数部分,偏置值是 127
  • Fraction(尾数位):7位,表示小数部分,也是隐含了首位的1,实际的尾数精度为8位

计算公式:

看一下在pytorch中是如何表示的:

import torch
torch.finfo(torch.bfloat16)
# 结果
finfo(resolution=0.01, min=-3.38953e+38, max=3.38953e+38, eps=0.0078125, smallest_normal=1.17549e-38, tiny=1.17549e-38, dtype=bfloat16)

每个字段的含义和上述是一致的,主要注意的是bfloat16的10进制间隔精度是0.01(注:在-1~1之间精度是0.001),表示范围是[-3.40282e+38,3.40282e+38]。可以明显的看到bfloat16比float16精度降低了但是表示的范围更大了,能够有效的防止在训练过程中的溢出。

3、FP32

FP32也叫做 float32,全称是Single-precision floating-point(单精度浮点数),在IEEE 754标准中是叫做binary32,简单来说是用32位二进制来表示的浮点数:

其中:

  • Sign(符号位): 1 位,0表示整数;1表示负数
  • Exponent(指数位):8位,表示整数部分,偏置值是 127
  • Fraction(尾数位):23位,表示小数部分,也是隐含了首位的1,实际的尾数精度为24位

计算公式:

在pytorch中的表示:

import torch
torch.finfo(torch.float32)
# 结果
finfo(resolution=1e-06, min=-3.40282e+38, max=3.40282e+38, eps=1.19209e-07, smallest_normal=1.17549e-38, tiny=1.17549e-38, dtype=float32)

每个字段的含义和上述是一致的,主要注意的是float32的10进制间隔精度是0.000001(注:在-1~1之间精度是0.0000001),表示范围是[-3.40282e+38,3.40282e+38]。可以看到float32精度又高,范围又大,可是32位的大小对于现在大模型时代的参数量太占空间了。

4、不同精度的显存占用

以显卡NVIDIA A40 48G,模型用llama-2-7b-hf,这个模型保存的精度通过查看模型文件的congfig.json可以看到是"torch_dtype": "float16"。

打印相关的版本和显卡信息:

import transformers
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

# 打印版本号
print("transformers version:", transformers.__version__)
print("torch version:", torch.__version__)

# 检查系统中是否有可用的 GPU
if torch.cuda.is_available():
    # 获取可用的 GPU 设备数量
    num_devices = torch.cuda.device_count()
    print("可用 GPU 数量:", num_devices)

    # 遍历所有可用的 GPU 设备并打印详细信息
    for i in range(num_devices):
        device = torch.cuda.get_device_properties(i)
        print(f"\nGPU {i} 的详细信息:")
        print("名称:", device.name)
        print("计算能力:", f"{device.major}.{device.minor}")
        print("内存总量 (GB):", round(device.total_memory / (1024**3), 1))
else:
    print("没有可用的 GPU")

# 结果
transformers version: 4.32.1
torch version: 2.0.1+cu117
可用 GPU 数量: 1

GPU 0 的详细信息:
名称: NVIDIA A40
计算能力: 8.6
内存总量 (GB): 44.4

然后用transformers加载模型,精度设为float16:

# 加载模型
model_name = "/path/to/llama-2-7b-hf" # 你模型存放的位置
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="cuda:0", torch_dtype=torch.float16)

加载模型后,查看模型的总参数:

total_parameters = model.num_parameters()
print("Total parameters in the model:", total_parameters)

# 结果
Total parameters in the model: 6738415616 # 6.73B

以float16进行加载,也就是每个参数16个bit,2个byte,计算一下参数占多少显存:

# 计算每个参数的大小(以字节为单位)
size_per_parameter_bytes = 2

# 计算模型在显存中的总空间(以字节为单位)
total_memory_bytes = total_parameters * size_per_parameter_bytes

# 将字节转换为更常见的单位(GB)
total_memory_gb = total_memory_bytes / (1024**3)

print("Total memory occupied by the model in MB:", total_memory_gb)

# 结果
Total memory occupied by the model in GB: 12.551277160644531

打印GPU上模型的显存占用:

# 计算模型的显存占用
memory_allocated = torch.cuda.memory_allocated(device='cuda:0')

# 将字节转换为更常见的单位(GB)
memory_allocated_gb = memory_allocated / (1024**3)

print("Memory allocated by the model in GB:", memory_allocated_gb)

# 结果
Memory allocated by the model in GB: 12.582542419433594

再看一下使用bfloat16加载的结果:

# 加载模型float32
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="cuda:0", torch_dtype=torch.bfloat16)

......

# 结果
Total memory occupied by the model in GB: 12.551277160644531
Memory allocated by the model in GB: 12.582542419433594

可以看到bfloat16和float16占用的显存是完全一样的。

看一下在float32下加载的情况:

# 加载模型float32
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="cuda:0", torch_dtype=torch.float32)

......

# 结果
Total memory occupied by the model in GB: 25.102554321289062
Memory allocated by the model in GB: 25.165069580078125

5、不同精度之间的转换

从huggingface下载的llama-2-7b-hf模型,通过查看模型文件的congfig.json可以看到是"torch_dtype": "float16",那为何加载的时候可以指定float32和bfloat16呢?是如何转化的呢?

在加载模型时torch内置了转换函数,将模型的每一个参数进行类型转换,如下:

# 对应float32
def float(self: T) -> T:
    r"""Casts all floating point parameters and buffers to ``float`` datatype.

    .. note::
        This method modifies the module in-place.

    Returns:
        Module: self
    """
    return self._apply(lambda t: t.float() if t.is_floating_point() else t)
# 对应float16
def half(self: T) -> T:
    r"""Casts all floating point parameters and buffers to ``half`` datatype.

    .. note::
        This method modifies the module in-place.

    Returns:
        Module: self
    """
    return self._apply(lambda t: t.half() if t.is_floating_point() else t)
# 对应bfloat16
def bfloat16(self: T) -> T:
    r"""Casts all floating point parameters and buffers to ``bfloat16`` datatype.

    .. note::
        This method modifies the module in-place.

    Returns:
        Module: self
    """
    return self._apply(lambda t: t.bfloat16() if t.is_floating_point() else t)

也可以在加载完模型后,使用上述函数手动转化模型参数类型,将float32转化为float16:

# 以float32加载
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="cuda:0", torch_dtype=torch.float32)
# 计算模型的显存占用
memory_allocated = torch.cuda.memory_allocated(device='cuda:0')
# 将字节转换为更常见的单位(GB)
memory_allocated_gb = memory_allocated / (1024**3)
print("Memory allocated by the model in GB:", memory_allocated_gb)

# 转为float16
model.half()
# 计算模型的显存占用
memory_allocated = torch.cuda.memory_allocated(device='cuda:0')
# 将字节转换为更常见的单位(GB)
memory_allocated_gb = memory_allocated / (1024**3)
print("Memory allocated by the model in GB:", memory_allocated_gb)

# 结果
Memory allocated by the model in GB: 25.165069580078125
Total memory occupied by the model in GB: 12.551277160644531

同样的,将float16转化为float32:

# 以float16加载
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="cuda:0", torch_dtype=torch.float16)
# 计算模型的显存占用
memory_allocated = torch.cuda.memory_allocated(device='cuda:0')
# 将字节转换为更常见的单位(GB)
memory_allocated_gb = memory_allocated / (1024**3)
print("Memory allocated by the model in GB:", memory_allocated_gb)

# 转为float32
model.float()
# 计算模型的显存占用
memory_allocated = torch.cuda.memory_allocated(device='cuda:0')
# 将字节转换为更常见的单位(GB)
memory_allocated_gb = memory_allocated / (1024**3)
print("Memory allocated by the model in GB:", memory_allocated_gb)

# 结果
Memory allocated by the model in GB: 12.582542419433594
Total memory occupied by the model in GB: 25.165069580078125

转换函数的底层是用 C++(CPU) 和 CUDA(GPU) 实现的:

举一个将单精度浮点数(float32)转换为半精度浮点数(float16)的例子。

假设有一个单精度浮点数二进制为0 10000000 10010001111010111000011,表示的十进制值为3.1400。

符号位

  • 单精度浮点数的符号位为0,表示正数。

指数位

  • 将单精度浮点数的8位指数位的偏移量减去127再加上15,得到半精度浮点数的5位指数位,单精度浮点数的指数位为128(二进制表示为10000000),则半精度浮点数的指数位为128-127+15=16(二进制表示为10000)。

尾数位

  • 将单精度浮点数的23位尾数位截取为10位,作为半精度浮点数的尾数位。(由于在截取的时候需要根据第 11 位及其后面的位数进行舍入,尾数的第 11 位为 1,根据 IEEE 754 舍入规则(向偶数舍入),我们需要将截取的尾数位加 1。)

合并符号、指数和尾数

  • 合并后的半精度浮点数为0 10000 1001001001000

 转换后的半精度浮点数二进制表示对应的十进制值约为3.140625。

看一下torch的结果:

import torch
# 创建一个单精度浮点数的张量
float_tensor = torch.tensor([3.14], dtype=torch.float32)
# 将张量转换为半精度浮点数
half_tensor = float_tensor.half()
# 打印转换后的张量及其数据类型
print("Original Tensor:\n", float_tensor)
print("Half-Precision Tensor:\n", half_tensor)

# 结果
Original Tensor:
 tensor([3.1400])
Half-Precision Tensor:
 tensor([3.1406], dtype=torch.float16)

可以看到,当 float32 转化成 float16 时,表示的十进制数出现了精度偏差。

继续看一下将半精度浮点数(float16)转换为单精度浮点数(float32),假设有一个半精度浮点数:0 01101 1010000000,其十进制值为约3.14,float16为3.140625。

符号位

  • 半精度浮点数的符号位为0,表示正数。将符号位复制到单精度浮点数,仍为0。

指数位

  • 半精度浮点数的5位指数位为01101,十进制值为13,偏移量调整:13 - 15 + 127 = 125(十进制),将125转换为8位二进制数为01111101,扩展到单精度浮点数的8位指数位为01111101

尾数位

  • 半精度浮点数的10位尾数位为1010000000
  • 扩展到单精度浮点数的23位尾数位为10100000000000000000000

合并符号、指数和尾数

  • 合并后的单精度浮点数为0 01111101 10100000000000000000000

转换后的单精度浮点数表示的十进制值约为3.140625,torch中:

import torch

# 创建一个半精度浮点数的张量
float_tensor = torch.tensor([3.14], dtype=torch.float16)

# 将张量转换为单精度浮点数
single_tensor = float_tensor.float()

# 打印转换后的张量及其数据类型
print("Original Tensor:\n", float_tensor)
print("Single-Precision Tensor:\n", single_tensor)

# 结果
Original Tensor:
 tensor([3.1406], dtype=torch.float16)
Single-Precision Tensor:
 tensor([3.1406])

通过上面的例子也可以看出float32精度是要高于float16的,因为 float32 转化成 float16 有可能存在精度损失。


总结

本文从三种精度的详解概念和计算,到每种精度占用显存的计算方式,最后介绍精度之间的相互转换,更深刻理解LLM在训练/推理时的参数类型的意义。

标签:显存,BF16,FP32,浮点数,torch,num,memory,allocated,float16
From: https://blog.csdn.net/m0_68116052/article/details/143840925

相关文章

  • Transformers显存优化策略
    (原创)Transformers显存优化简易策略(本教程目标:4G显存也能跑BERT-Large)......
  • OFA-Sys/chinese-clip-vit-base-patch16 占用显存测试
    model.get_image_features(inputs) 64batch_size2096MB取消withtorch.no_grad():后8GB占满16batch_size3886MB AutoModel.from_pretrained(MODEL_NAME)执行慢,原因是需要启用网络代理,否则总是卡在验证阶段 DataLoader增加num_workers后torch.cuda.OutOf......
  • 大模型训练显存需求分析指南:从SFT到RLHF的实践之路
    引言随着大模型技术的快速发展,越来越多的研究者和开发者开始尝试自己训练或微调大模型。然而,大模型训练最大的门槛之一就是算力资源,特别是GPU显存的需求。本文将从实践角度出发,详细分析大模型训练中的显存需求,帮助读者更好地规划自己的训练资源。显存需求概览在大模型训......
  • 【基础岛·第2关】8G 显存玩转书生大模型 Demo
    目录创建开发机环境配置CliDemo部署InternLM2-Chat-1.8B模型创建开发机我们选择10%的开发机,镜像选择为Cuda-12.2。在输入开发机名称后,点击创建开发机环境配置在/root/share/pre_envs中配置好了预置环境icamp3_demo可以通过如下指令进行激活:condaactivate/root......
  • 【ComfyUI工作流】神级AI文生图Flux.1本地一键部署整合包,6G显存NSFW版本​
    FLUX.1是由BlackForestLabs精心研发的AI图像生成模型,其强大的文本到图像的转换能力,让梦想变得触手可及。然而,FLUX.1模型对硬件的要求极为苛刻,尤其是显存需求高达42GB,这使得大多数普通用户难以直接运行该模型。为了解决这一难题,开发者们推出了FLUX.1GGUF版本。GGUF(GPT-Generated......
  • 腾讯混元文生图开源模型推出小显存版本,仅需6G显存即可运行
    腾讯混元文生图开源模型推出小显存版本,仅需6G显存即可运行7月4日,腾讯混元文生图大模型(混元DiT)宣布开源小显存版本,仅需6G显存即可运行,对使用个人电脑本地部署的开发者十分友好,该版本与LoRA、ControlNet等插件,都已适配至Diffusers库;并新增对Kohya图形化界面的支持,让开发者可......
  • 为大模型提供服务需要多少 GPU 显存?
    在几乎所有的LLM面试中,有一个问题总是会被提及:“**为大模型提供服务需要多少GPU显存?**”这不仅仅是一个随机的问题——它是一个关键指标,反映了你对这些强大模型在生产环境中部署和可扩展性的理解程度。当你使用GPT、LLaMA或任何其他LLM时,了解如何估算所需的GPU内存是至......
  • 开源大模型占GPU显存计算方法
    运行大模型GPU占用计算公式:\(M=\frac{(P*4B)}{32/Q}*1/2\)M:以GB标识的GPU内存P:模型中的参数数量,例如一个7B模型有70亿参数4B:4个字节,表示用于每个参数的字节32:4个字节中有32位Q:应该用于加载模型的位数,例如16位、8位、4位1.2:表示在GPU内存中加载其......
  • 8G 显存玩转书生大模型 Demo
    8G显存玩转书生大模型Demo首先第一步依旧是创建我们的开发机,选择上我们需要选择10%的开发机,镜像选择为Cuda-12.2。在输入开发机名称后,点击创建开发机。这里就不放创建的流程图了环境配置#创建环境condacreate-ndemopython=3.10-y#激活环境condaactivate......
  • 神经网络释放GPU显存两种方式(固定or动态)
    固定的批次数后释放显存固定的批次数后释放显存,比如每训练100批次释放一次显存,可以通过在训练循环中添加一个计数器来实现。以下是如何实现这种策略的示例代码:importtorchdeftrain():start_epoch=0end_epoch=100release_frequency=100#每100个批次......