前言
随着DeepSeek爆火,面试中也越来越高频出现,因此训练营也更新了DeepSeek系列技术的深入拆解。包括MLA、MTP、专家负载均衡、FP8混合精度训练,Dual-Pipe等关键技术,力求做到全网最硬核的解析~
本篇文章主要对训练 LLM 以及部署应用时的精度问题进行了一些探讨和实践,读过后应该会对常用的浮点数 FP16,FP32,BF16 有一个更好的理解~
浮点数据类型在 IEEE 754-2019(2008) 标准中进行了详细的定义,定义了不同精度的浮点数格式,如 binary16、binary32 和 binary64,分别用 16 位、32 位和 64 位二进制来表示。
想要更全方位深入的了解的话,可以点引用查看官方的 paper。下面进行一些常用的浮点数介绍。
1、FP16
FP16 也叫做 float16,两种叫法是完全一样的,全称是 Half-precision floating-point(半精度浮点数),在 IEEE 754 标准中是叫做 binary16。
简单来说是用 16 位二进制来表示的浮点数,来看一下是怎么表示的(以下图都来源于维基百科):
其中:
# 第一种计算方式
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) = 1.5625
# 第二种计算方式
1.1001000000 = 1 + 576(1001000000变成10进制)/1024 = 1.5625
所以正常情况下计算公式就是:
举一个例子来计算,这个是 FP16(float16) 能表示的最大的正数:
同样,这个是 FP16(float16) 能表示的最大的负数:
这就是 FP16(float16) 表示的范围 [-65504,65504]。
我们来看一些特殊情况,FP16(float16) 能表示最小的正数是多少呢?
我们就不一一的计算了,贴一个 FP16(float16) 特殊数值的情况:
上表中,subnormal number 是指指数位为全 0 的特殊情况情况,其他的也是一些常见的特殊情况。
接下来看一下在 pytorch 中是如何表示的:
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)
一些解释:
-
resolution(分辨率):这个浮点数类型的在十进制上的分辨率,表示两个不同值之间的最小间隔。对于
torch.float16
,分辨率是 0.001,就是说两个不同的torch.float16
数值之间的最小间隔是 0.001。 -
min(最小值):对于
torch.float16
,最小值是 -65504。 -
max(最大值):对于
torch.float16
,最大值是 65504。 -
eps(机器精度):机器精度表示在给定数据类型下,比 1 大的最小浮点数,对于
torch.float16
,机器精度是 0.000976562,对应上表中的 smallest number larger than one。 -
smallest_normal(最小正规数):最小正规数是大于零的最小浮点数,对于
torch.float16
,最小正规数是 6.10352e-05,对应上表中的 smallest positive normal number -
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
因为在计算机中是以 2 进制存储计算的,所以转换后的 float16 值会有很多位小数,但这些后面的小数是没有精度的,换成 10 进制的精度是只有 0.001 的。
注:在 -1~1 之间精度是 0.0001,因为有隐含位1的关系,大家可以试一下。
2、BF16
BF16 也叫做 bfloat16(这是最常叫法),其实叫“BF16”不知道是否准确,全称 brain floating point,也是用 16 位二进制来表示的,是由 Google Brain 开发的,所以这个 brain 应该是 Google Brain 的第二个单词。
和上述 FP16 不一样的地方就是指数位和尾数位不一样,看图:
-
Sign(符号位):1 位,0 表示整数;1 表示负数
-
Exponent(指数位):8 位,表示整数部分,偏置值是 127
-
Fraction(尾数位):7 位,表示小数部分,也是隐含了首位的 1,实际的尾数精度为 8 位
计算公式:
这里要注意一下,并不是所有的硬件都支持 bfloat16,因为它是一个比较新的数据类型,在 NVIDIA GPU 上,只有 Ampere 架构以及之后的 GPU 才支持。
如何判断呢?很简单:
import transformers
transformers.utils.import_utils.is_torch_bf16_gpu_available()
# 结果为True就是支持
看一下在 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 精度降低了,但是表示的范围更大了,能够有效的防止在训练过程中的溢出。有兴趣的同学可以用上述代码实验一下 bfloat16~
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 位的大小对于现在大模型时代的参数量太占空间了。
以上就是对常见 FP16,FP32,BF16 精度的浮点数的一点介绍,后续会围绕下面三个问题再进行讲解:
-
大模型中不同精度占用的显存大小?
-
大模型中不同精度之间如何转换?
-
模型训练中的混合精度是什么?
最后
为了助力朋友们跳槽面试、升职加薪、职业困境,提高自己的技术,本文给大家整了一套涵盖AI大模型所有技术栈的快速学习方法和笔记。目前已经收到了七八个网友的反馈,说是面试问到了很多这里面的知识点。
由于文章篇幅有限,不能将全部的面试题+答案解析展示出来,有需要完整面试题资料的朋友,可以扫描下方二维码免费领取哦!!! 👇👇👇👇

面试题展示
1、请解释一下BERT模型的原理和应用场景。
答案:BERT(Bidirectional Encoder Representations from Transformers)是一种预训练的语言模型,通过双向Transformer编码器来学习文本的表示。它在自然语言处理任务中取得了很好的效果,如文本分类、命名实体识别等。
2、什么是序列到序列模型(Seq2Seq),并举例说明其在自然语言处理中的应用。
答案:Seq2Seq模型是一种将一个序列映射到另一个序列的模型,常用于机器翻译、对话生成等任务。例如,将英文句子翻译成法文句子。
3、请解释一下Transformer模型的原理和优势。
答案:Transformer是一种基于自注意力机制的模型,用于处理序列数据。它的优势在于能够并行计算,减少了训练时间,并且在很多自然语言处理任务中表现出色。
4、什么是注意力机制(Attention Mechanism),并举例说明其在深度学习中的应用。
答案:注意力机制是一种机制,用于给予模型对不同部分输入的不同权重。在深度学习中,注意力机制常用于提升模型在处理长序列数据时的性能,如机器翻译、文本摘要等任务。
5、请解释一下卷积神经网络(CNN)在计算机视觉中的应用,并说明其优势。
答案:CNN是一种专门用于处理图像数据的神经网络结构,通过卷积层和池化层提取图像特征。它在计算机视觉任务中广泛应用,如图像分类、目标检测等,并且具有参数共享和平移不变性等优势。
6、请解释一下生成对抗网络(GAN)的原理和应用。
答案:GAN是一种由生成器和判别器组成的对抗性网络结构,用于生成逼真的数据样本。它在图像生成、图像修复等任务中取得了很好的效果。
7、请解释一下强化学习(Reinforcement Learning)的原理和应用。
答案:强化学习是一种通过与环境交互学习最优策略的机器学习方法。它在游戏领域、机器人控制等领域有广泛的应用。
8、请解释一下自监督学习(Self-Supervised Learning)的原理和优势。
答案:自监督学习是一种无需人工标注标签的学习方法,通过模型自动生成标签进行训练。它在数据标注困难的情况下有很大的优势。
9、解释一下迁移学习(Transfer Learning)的原理和应用。
答案:迁移学习是一种将在一个任务上学到的知识迁移到另一个任务上的学习方法。它在数据稀缺或新任务数据量较小时有很好的效果。
10、请解释一下模型蒸馏(Model Distillation)的原理和应用。
答案:模型蒸馏是一种通过训练一个小模型来近似一个大模型的方法。它可以减少模型的计算和存储开销,并在移动端部署时有很大的优势。
11、请解释一下LSTM(Long Short-Term Memory)模型的原理和应用场景。
答案:LSTM是一种特殊的循环神经网络结构,用于处理序列数据。它通过门控单元来学习长期依赖关系,常用于语言建模、时间序列预测等任务。
12、请解释一下BERT模型的原理和应用场景。
答案:BERT(Bidirectional Encoder Representations from Transformers)是一种预训练的语言模型,通过双向Transformer编码器来学习文本的表示。它在自然语言处理任务中取得了很好的效果,如文本分类、命名实体识别等。
13、什么是注意力机制(Attention Mechanism),并举例说明其在深度学习中的应用。
答案:注意力机制是一种机制,用于给予模型对不同部分输入的不同权重。在深度学习中,注意力机制常用于提升模型在处理长序列数据时的性能,如机器翻译、文本摘要等任务。
14、请解释一下生成对抗网络(GAN)的原理和应用。
答案:GAN是一种由生成器和判别器组成的对抗性网络结构,用于生成逼真的数据样本。它在图像生成、图像修复等任务中取得了很好的效果。
15、请解释一下卷积神经网络(CNN)在计算机视觉中的应用,并说明其优势。
答案:CNN是一种专门用于处理图像数据的神经网络结构,通过卷积层和池化层提取图像特征。它在计算机视觉任务中广泛应用,如图像分类、目标检测等,并且具有参数共享和平移不变性等优势。
16、请解释一下强化学习(Reinforcement Learning)的原理和应用。
答案:强化学习是一种通过与环境交互学习最优策略的机器学习方法。它在游戏领域、机器人控制等领域有广泛的应用。
17、请解释一下自监督学习(Self-Supervised Learning)的原理和优势。
答案:自监督学习是一种无需人工标注标签的学习方法,通过模型自动生成标签进行训练。它在数据标注困难的情况下有很大的优势。
18、请解释一下迁移学习(Transfer Learning)的原理和应用。
答案:迁移学习是一种将在一个任务上学到的知识迁移到另一个任务上的学习方法。它在数据稀缺或新任务数据量较小时有很好的效果。
19、请解释一下模型蒸馏(Model Distillation)的原理和应用。
答案:模型蒸馏是一种通过训练一个小模型来近似一个大模型的方法。它可以减少模型的计算和存储开销,并在移动端部署时有很大的优势。
20、请解释一下BERT中的Masked Language Model(MLM)任务及其作用。
答案:MLM是BERT预训练任务之一,通过在输入文本中随机mask掉一部分词汇,让模型预测这些被mask掉的词汇。
由于文章篇幅有限,不能将全部的面试题+答案解析展示出来,有需要完整面试题资料的朋友,可以扫描下方二维码免费领取哦!!! 👇👇👇👇
