当前位置: 首页 > news >正文

一文学懂快浮点数据格式

块浮点(Block Floating-Point, BFP)

是一种数据表示方法,它结合了传统浮点数和整数量化的优势,特别适用于深度学习应用中以提高计算效率和减少存储需求。在BFP格式中,一组数值共享同一个指数部分,而每个数值的尾数则独立保存。这意味着,在一个块内的所有数字都使用相同的缩放因子,这与传统的浮点数不同,后者为每个数值分配了一个独立的指数。这种设计允许BFP在保持较高数值保真度的同时,显著减少了所需的比特数。例如,在硬件实现中,BFP的缩放因子通常被限制为2的幂次,这样不仅便于硬件实现,还能有效减少异常值的影响。

进一步地,BFP通过引入两级缩放策略来优化其性能。首先,初始值集合根据数据分布使用较为“昂贵”的FP32重新缩放步骤进行全局缩放,形成预缩放分区。然后,这些分区可以进一步使用低成本、2的幂次表示的缩放因子进行细粒度调整。这种方法能够在有限比特数的情况下构建高保真度的数据表示,同时最小化计算和内存开销,并接近全精度缩放的效果。此外,BFP还能够很好地集成到现有的标准浮点点积流水线中,这意味着它可以利用现有电路设计,降低边际成本。通过这种方式,BFP不仅提高了计算效率,而且减少了量化误差,使得模型在低比特宽度下依然能保持较高的准确性。总之,BFP提供了一种平衡数值精度和计算效率的有效方法,尤其适合用于需要高效处理大量数据的深度学习场景。

减少所需的比特数

块浮点(Block Floating-Point, BFP)的设计允许在保持较高数值保真度的同时显著减少所需的比特数。其核心思想是:一组相邻的数值共享同一个指数(exponent),而每个数值只存储自己的尾数(mantissa)。这种方式相比于传统的浮点格式(如FP32或FP16),避免了为每个数值单独存储指数,从而节省了大量比特。

我们通过一个具体的例子来说明:


假设我们有以下4个浮点数(以二进制科学计数法表示):

x1 = 1.0 * 2**3
x2 = 1.5 * 2**3
x3 = 0.75 * 2**3
x4 = 2.0 * 2**3

这些数的共同指数是 3,只有尾数不同。如果我们用传统 FP32 格式存储这四个数,每个数需要 32 bit,总共需要:

total_bits_fp32 = 4 * 32 = 128 bits

但如果使用 BFP 格式,我们可以只存储一次公共指数(例如用 8 bits 表示),然后分别存储每个数的尾数(例如每个尾数用 5 bits 表示),那么总比特数为:

total_bits_bfp = 8 + 4 * 5 = 28 bits

可以看到,BFP 所需的比特数仅为传统浮点数的 21.9%,极大地节省了存储空间和带宽。


更重要的是,这种设计并没有明显牺牲精度。因为尾数仍然保留了足够的位数来表示每个数值的细节,只是共享了一个统一的指数。只要这组数值的动态范围相近(即它们大致处于同一数量级),共享指数就不会导致显著的精度损失。

再举一个更贴近深度学习的例子:

在神经网络中,某一层的激活值可能如下所示:

a = [0.125, 0.25, 0.5, 1.0, 2.0, 4.0]

这些数的指数都在 2⁻³ 到 2² 之间。如果我们选择一个合适的公共指数(比如取最大指数 e = 2),然后对所有数值进行缩放,可以得到:

scaled_a = [0.015625 * 2**2, 0.0625 * 2**2, 0.125 * 2**2, 0.25 * 2**2, 0.5 * 2**2, 1.0 * 2**2]

这样就可以用一个统一的指数 e=2 和各自的尾数来表示整个数组,进一步压缩数据表示的位宽。


综上所述,块浮点(BFP)通过共享一组数值的指数部分,显著减少了总的比特数,同时又通过独立存储每个数值的尾数,保留了足够的数值精度。这种方法特别适用于深度学习等大规模并行计算场景,在保证模型性能的前提下,大幅提升了硬件效率和内存利用率。

MX

块浮点(Block Floating-Point, BFP)和共享微指数(Shared Microexponents, MX)都是为了解决深度学习中高效计算和存储的需求而设计的数据表示方法,但它们在实现细节上有所不同。BFP允许一组数值共享同一个指数部分,而每个数值的尾数则独立保存,从而减少了存储单独指数所需的空间。相比之下,MX格式进一步细化了这种概念,通过引入硬件支持的细粒度缩放因子来减少数值噪声,并优化硅片成本。

具体来说,在BFP中,假设我们有n个数x₁, x₂, ..., xₙ,可以表示为:

# 假设有n个数,表示如下:
x = [m1 * 2**e for m1 in range(1, n+1)]
# 其中mi是尾数,e是公共指数。
# 如果使用传统浮点表示法,每个数需要32位,总共需要:
total_bits_fp32 = 32 * n# 使用BFP格式时,我们只需要一个公共指数(例如8位),以及每个数的独立尾数(假设每个尾数需要5位)
public_exponent_bits = 8
mantissa_bits_per_number = 5
total_bits_bfp = public_exponent_bits + mantissa_bits_per_number * n

然而,MX格式采用了更加细粒度的缩放策略。它不仅共享一个全局的指数,还为每个小分块内的数值分配了一个额外的微指数。这意味着MX格式能够更精确地调整每个数值的实际大小,减少量化误差。以MX为例,其数据块由一个共享尺度X和k个标量元素{Pᵢ}组成,每个元素表示为XPᵢ。这里的关键在于MX格式中的每个元素可以根据需要应用不同的微指数,从而更好地适应数据分布的变化。例如:

# 假设有一个MX块,包含4个元素,每个元素的表示如下:
element_values = [v1, v2, v3, v4]
# 共享尺度X
shared_exp = 3  # 示例值
X = 2 ** shared_exp
# 每个元素的值为 XPi,其中Pi是根据元素数据格式进行量化后的结果
P = [0.125, 0.25, 0.5, 1.0]  # 示例值
values_in_mx_format = [X * p for p in P]# 对于MX格式,每个块都有一个共享的指数X和多个标量元素Pi
# 例如,对于一个具体的MX块,我们可以定义如下:
block_size = 4
scale_data_format = 'E8M0'  # 示例格式
element_data_format = 'FP8(E4M3)'  # 示例格式
elements = [0.125, 0.25, 0.5, 1.0]  # 示例元素值# 将这些元素转换为MX格式
def convert_to_mx_format(elements):max_val = max(abs(v) for v in elements)shared_exp = int(max_val).bit_length() - 1  # 简化处理X = 2 ** shared_expPi = [v / X for v in elements]return X, PiX, Pi = convert_to_mx_format(elements)
print(f"共享尺度X: {X}, 元素值Pi: {Pi}")

MX格式的一个显著特点是它可以在不牺牲模型准确性的前提下,显著减少所需的比特数,并提高硬件效率。这是因为MX格式能够提供比BFP更高的数值保真度,特别是在处理具有较大动态范围的数据集时。此外,MX格式利用硬件支持的微指数来自动设置所有缩放因子,无需复杂的软件干预。

因此,尽管BFP和MX都旨在通过共享指数来优化数据表示,MX通过引入更细粒度的缩放机制,进一步提高了数值精度和硬件效率。这使得MX格式在AI硬件加速电路中尤为重要,因为它能够在保持或接近原有模型准确性的同时,大幅降低计算和存储需求。正因为如此,支持MX格式的AI硬件加速电路对于现代深度学习系统的性能提升至关重要。

综上所述,MX与BFP相比,不仅继承了共享指数的优点,还通过细粒度的微指数调整,提供了更高的灵活性和精度,使其成为高效AI硬件设计的理想选择。

细粒度

好的,让我们通过一个具体的例子来详细解释 MX(Microscaling) 的流程,并展示细粒度缩放的过程。我们将使用一组具体的数值,并通过代码逐步演示如何将这些数值转换为 MX 格式。

具体例子

假设我们有一个包含4个浮点数的列表 elements

elements = [0.125, 0.25, 0.5, 1.0]

我们将通过以下步骤将其转换为 MX 格式,并展示细粒度缩放的过程。

步骤一:找出最大值

首先,我们需要找出 elements 列表中绝对值最大的元素。

max_val = max(abs(v) for v in elements)
# 对于 elements = [0.125, 0.25, 0.5, 1.0]:
# abs(0.125) = 0.125
# abs(0.25) = 0.25
# abs(0.5) = 0.5
# abs(1.0) = 1.0
# max_val = max([0.125, 0.25, 0.5, 1.0]) = 1.0

步骤二:计算共享指数

接下来,我们根据最大值 max_val 计算共享指数 shared_exp。这里我们使用简化的方法来估算 log2(max_val)

shared_exp = int(max_val).bit_length() - 1
# 对于 max_val = 1.0:
# int(1.0) = 1
# 1.bit_length() = 1
# shared_exp = 1 - 1 = 0

步骤三:计算共享尺度 X

根据共享指数 shared_exp,我们可以计算共享尺度 X

X = 2 ** shared_exp
# 对于 shared_exp = 0:
# X = 2 ** 0 = 1.0

步骤四:计算每个元素的 Pi 值

然后,我们将每个原始值除以共享尺度 X,得到归一化后的值 Pi

Pi = [v / X for v in elements]
# 对于 elements = [0.125, 0.25, 0.5, 1.0] 和 X = 1.0:
# Pi = [0.125 / 1.0, 0.25 / 1.0, 0.5 / 1.0, 1.0 / 1.0]
# Pi = [0.125, 0.25, 0.5, 1.0]

步骤五:返回结果

最终,我们返回共享尺度 X 和归一化后的值 Pi

return X, Pi
# 返回结果:X = 1.0, Pi = [0.125, 0.25, 0.5, 1.0]

细粒度缩放的例子

为了展示细粒度缩放的效果,我们假设 elements 列表中的数值动态范围更大一些。例如:

elements = [0.125, 0.25, 0.5, 8.0]
步骤一:找出最大值
max_val = max(abs(v) for v in elements)
# 对于 elements = [0.125, 0.25, 0.5, 8.0]:
# abs(0.125) = 0.125
# abs(0.25) = 0.25
# abs(0.5) = 0.5
# abs(8.0) = 8.0
# max_val = max([0.125, 0.25, 0.5, 8.0]) = 8.0
步骤二:计算共享指数
shared_exp = int(max_val).bit_length() - 1
# 对于 max_val = 8.0:
# int(8.0) = 8
# 8.bit_length() = 4
# shared_exp = 4 - 1 = 3
步骤三:计算共享尺度 X
X = 2 ** shared_exp
# 对于 shared_exp = 3:
# X = 2 ** 3 = 8.0
步骤四:计算每个元素的 Pi 值
Pi = [v / X for v in elements]
# 对于 elements = [0.125, 0.25, 0.5, 8.0] 和 X = 8.0:
# Pi = [0.125 / 8.0, 0.25 / 8.0, 0.5 / 8.0, 8.0 / 8.0]
# Pi = [0.015625, 0.03125, 0.0625, 1.0]
步骤五:返回结果
return X, Pi
# 返回结果:X = 8.0, Pi = [0.015625, 0.03125, 0.0625, 1.0]

细粒度缩放的实际应用

现在,我们来看一下细粒度缩放的应用。假设我们要将这些 Pi 值量化到 FP8(E4M3) 格式。FP8(E4M3) 表示有4位指数和3位尾数的8位浮点数。

FP8(E4M3) 示例

对于每个 Pi 值,我们需要将其量化到 FP8(E4M3) 格式。假设我们使用简单的量化方法(实际硬件中会有更复杂的量化算法):

def quantize_to_fp8(value):# 简单的量化方法:直接截取前几位有效数字# 这里只是一个示意,实际实现会更复杂return round(value * (2 ** 3)) / (2 ** 3)quantized_Pi = [quantize_to_fp8(p) for p in Pi]
# 对于 Pi = [0.015625, 0.03125, 0.0625, 1.0]:
# quantized_Pi = [round(0.015625 * 8) / 8, round(0.03125 * 8) / 8, round(0.0625 * 8) / 8, round(1.0 * 8) / 8]
# quantized_Pi = [0.015625, 0.03125, 0.0625, 1.0]

在实际应用中,quantize_to_fp8 函数会更复杂,但这个简单示例展示了如何将值量化到特定格式。

总结

通过上述步骤,我们展示了如何将一组数值转换为 MX 格式,并引入了细粒度缩放的概念。具体来说:

  • 我们先找到最大值 max_val,并计算共享指数 shared_exp
  • 根据 shared_exp 计算共享尺度 X
  • 将每个原始值除以 X,得到归一化后的值 Pi
  • 最后,我们将 Pi 值量化到目标格式(如 FP8(E4M3))。

这种设计使得 MX 格式能够灵活适应不同的数据分布,并在保持较高数值保真度的同时显著减少了所需的比特数。相比 BFP,MX 通过细粒度缩放机制提供了更高的精度控制。

分层

在神经网络量化中,第一级块和第二级块是一种分层结构。第一级块是较大的划分单位,而第二级块是在第一级块内部进一步细分的小单位。这种结构允许在保持整体精度的同时进行细粒度的控制。

块粒度指的是每个块中包含多少个数值。第一级块粒度表示每个第一级块包含的元素数量,第二级块粒度表示在每个第一级块内部,再划分成的更小块的数量。

尺度位宽是指用于表示该块缩放因子(scale)的比特数。每个块都有一个共享的 scale,它决定了这个块内所有数值的动态范围。第一级和第二级可以有不同的 scale 位宽。

尾数位宽是指每个具体数值用多少比特来表示。它决定了每个数值的精度。

下面通过一个例子来说明这些概念:

假设我们有以下配置:

k1 = 16   # 第一级块大小为16个元素
k2 = 2    # 第二级块大小为2个元素
d1 = 8    # 第一级 scale 使用 8-bit
d2 = 1    # 第二级 scale 使用 1-bit
m = 7     # 每个数值的尾数部分使用 7-bit 表示

在这个例子中,第一级块包含16个元素,这些元素又被划分为8个第二级块,每个第二级块包含2个元素。每个第一级块使用8-bit来表示其scale,每个第二级块使用1-bit来表示其scale。每个数值的尾数部分使用7-bit来表示。

我们可以计算出每个元素平均占用的比特数:

average_bits_per_element = (d1 + m) / k1 + d2 / k2
# 即:
# average_bits_per_element = (8 + 7) / 16 + 1 / 2
# average_bits_per_element = 15/16 + 0.5 ≈ 0.9375 + 0.5 = 1.4375 bits per element

通过这种分级量化的设计,可以在不显著损失精度的前提下,大幅减少模型所需存储空间和计算资源。

分层数据

好的,下面我们将以一个具体的数值例子来说明 MX4 的两级量化结构。MX4 是一种混合精度量化方案,它采用两级块结构(two-level block structure),并结合尺度(scale)和尾数(mantissa)来表示数值。


我们假设有一个浮点数数组 x,其长度为 8,例如:

x = [3.14, 6.28, 9.42, 12.56, 15.7, 18.84, 21.98, 25.12]

这个数组可以看作是一个神经网络中的权重或激活值的一部分。我们希望用 MX4 格式对其进行压缩表示。

第一步:划分第一级块

在 MX4 中,第一级块大小为 k1 = 4,因此我们可以将上面的数组划分为两个第一级块:

block1_level1 = [3.14, 6.28, 9.42, 12.56]
block2_level1 = [15.7, 18.84, 21.98, 25.12]

每个第一级块都会对应一个第一级尺度(scale1),它是该块中所有元素的绝对最大值。我们计算如下:

scale1_block1 = max(abs(3.14), abs(6.28), abs(9.42), abs(12.56)) = 12.56
scale1_block2 = max(abs(15.7), abs(18.84), abs(21.98), abs(25.12)) = 25.12

然后,每个元素被归一化到 [-1, 1] 范围内,并使用 4-bit 尾数(m=4)进行表示。归一化公式如下:

normalized_x = x / scale1
quantized_x = round(normalized_x * (2**m - 1))

对第一个第一级块进行归一化与量化:

normalized_block1 = [3.14/12.56, 6.28/12.56, 9.42/12.56, 12.56/12.56]= [0.25, 0.5, 0.75, 1.0]quantized_block1 = [round(0.25 * 15), round(0.5 * 15), round(0.75 * 15), round(1.0 * 15)]= [4, 8, 11, 15]   # 每个数是 4-bit 表示

同理可得第二个第一级块的量化结果。

第二步:划分第二级块

接下来,在每个第一级块内部进一步划分为第二级块。MX4 中规定第二级块大小为 k2 = 2,所以每个第一级块(含4个元素)被划分为两个第二级块:

# block1_level1 划分:
block1_level2_1 = [3.14, 6.28]
block1_level2_2 = [9.42, 12.56]# block2_level1 划分:
block2_level2_1 = [15.7, 18.84]
block2_level2_2 = [21.98, 25.12]

每个第二级块也有自己的尺度(scale2),用于局部调整。例如:

scale2_block1_1 = max(abs(3.14), abs(6.28)) = 6.28
scale2_block1_2 = max(abs(9.42), abs(12.56)) = 12.56

此时每个第二级块内的元素可以用更细粒度的 scale 来表示,但它们的 scale 使用 2-bit 编码(d2=2),即只保留有限的动态范围。


第三步:总比特消耗计算

现在我们统计一下总共用了多少比特来表示这 8 个原始浮点数。

  • 第一级 scale 数量:2 个(每个 d1=4 bit) → 共 2 × 4 = 8 bits
  • 第二级 scale 数量:4 个(每个 d2=2 bit) → 共 4 × 2 = 8 bits
  • 所有元素的尾数:8 个(每个 m=4 bit) → 共 8 × 4 = 32 bits

总计:

total_bits = 8 + 8 + 32 = 48 bits
average_bits_per_element = total_bits / 8 = 6 bits per element

总结

在这个 MX4 示例中,我们通过将原始数据划分为两级块结构,分别设置了不同位宽的尺度和统一的尾数位宽,最终实现了高效的压缩表示。虽然原始数据是 32 位浮点型,但经过 MX4 量化后,平均每个元素仅需 6 位 存储,节省了约 81% 的存储空间,同时保持了较高的精度水平。这种结构非常适合边缘设备上的高效推理部署。

http://www.lryc.cn/news/571775.html

相关文章:

  • 【深度学习】卷积神经网络(CNN):计算机视觉的革命性引擎
  • 蚂蚁百宝箱+MCP打造p 人解放神器agent,解放大脑
  • 设置环境变量(linux,windows,windows用指令和用界面)
  • HarmonyOS性能优化——感知流畅优化
  • 鸿蒙网络编程系列54-仓颉版实现Smtp邮件发送客户端
  • LVS +Keepalived 高可用群集
  • 51c大模型~合集141
  • maven编译报错java: Compilation failed: internal java compiler error
  • 基于C++实现(控制台)机械提取词频
  • Hive的分区表(静态分区、动态分区)、分桶表、四种排序方式和数据加载方式
  • Linux操作系统之进程(六):进程的控制(上)
  • 鼎捷T100开发语言-Genero FGL 终极技术手册
  • Linux软件管理包-yum和基础开发工具-vim
  • 6.18 redis面试题 日志 缓存淘汰过期删除 集群
  • 【Leetcode】每日一题 —— No.2966
  • milvus和attu的搭建
  • 八种常见的神经网络介绍
  • Ubuntu 使用kubeadm部署k8s系统组件反复重启的问题
  • LVS +Keepalived高可用群集
  • 物联网控制技术期末复习 知识点总结 第六章 物联网控制算法(PID算法 PWM算法)
  • vscode连接不上服务器问题修复
  • 如何运用 AI 工具运营海外社媒账号
  • 借助AI学习编程,走向架构师之路
  • class对象【C#】2025复习
  • 排序算法专题
  • 【文本大模型】从0开始 - 本地部署一个ChatGLM对话模型(基于WebUI)
  • MySQL 索引和查询优化
  • linux虚拟机yum命令报错解决方案
  • 学习大模型---需要掌握的数学知识
  • 【Python编程】__all__ = [] 的作用