【问题标题】:Python numpy float16 datatype operations, and float8?Python numpy float16 数据类型操作和 float8?
【发布时间】:2016-08-16 13:04:32
【问题描述】:

在对 float16 Numpy 数字进行数学运算时,结果也是 float16 类型的数字。 我的问题是结果是如何计算的? 假设我将两个 float16 数字相乘/相加,python 是否在 float32 中生成结果,然后将结果截断/舍入到 float16?还是一直在“16 位多路复用器/加法器硬件”中执行的计算?

另一个问题 - 有 float8 类型吗?我找不到这个……如果没有,那为什么?谢谢大家!

【问题讨论】:

    标签: python numpy floating-point precision


    【解决方案1】:

    对于第一个问题:在典型的处理器上(至少在 GPU 之外)没有对 float16 的硬件支持。 NumPy 完全按照您的建议进行操作:将float16 操作数转换为float32,对float32 值执行标量运算,然后将float32 结果四舍五入为float16。可以证明,结果仍然是正确舍入的:float32 的精度足够大(相对于 float16),双舍入在这里不是问题,至少对于四个基本算术运算和平方根。

    在当前的 NumPy 源代码中,这就是 float16 标量运算的四种基本算术运算的定义。

    #define half_ctype_add(a, b, outp) *(outp) = \
            npy_float_to_half(npy_half_to_float(a) + npy_half_to_float(b))
    #define half_ctype_subtract(a, b, outp) *(outp) = \
            npy_float_to_half(npy_half_to_float(a) - npy_half_to_float(b))
    #define half_ctype_multiply(a, b, outp) *(outp) = \
            npy_float_to_half(npy_half_to_float(a) * npy_half_to_float(b))
    #define half_ctype_divide(a, b, outp) *(outp) = \
            npy_float_to_half(npy_half_to_float(a) / npy_half_to_float(b))
    

    上面的代码取自 NumPy 源代码中的scalarmath.c.src。您还可以查看 loops.c.src 以获取数组 ufunc 的相应代码。支持npy_half_to_floatnpy_float_to_half 函数在halffloat.c 中定义,以及float16 类型的各种其他支持函数。

    对于第二个问题:不,NumPy 中没有 float8 类型。 float16 是一种标准化类型(在 IEEE 754 标准中描述),它已经在某些情况下(尤其是 GPU)广泛使用。没有 IEEE 754 float8 类型,而且似乎没有明显的“标准”float8 类型候选者。我还猜想 NumPy 中对 float8 支持的需求并不大。

    【讨论】:

    • 那么,这意味着 Numpy 的 float16 计算结果不会与使用 float16 硬件计算的结果完全相同吗?对吗?
    • @JonyK:不,恰恰相反。 :-) 对于四种基本算术运算和平方根,结果应该完全与由符合 IEEE 754 的硬件直接以 float16 形式计算的结果相同(至少,对于默认关系到偶数舍入模式)。这并不完全明显:它需要对每个操作进行简短的证明,这取决于 float32 的精度是 float16 的两倍多一点的事实,但这是真的。 (相比之下,将 80 位 x87 扩展精度类型与 float64 结合可能会导致双舍入问题。)
    • @JonyK:我会在今天晚些时候(下班后)找时间添加上述证明的草图。同时,尝试在 Google 上搜索“双舍入何时无害?”
    【解决方案2】:

    此答案基于问题的float8 方面。公认的答案很好地涵盖了其余部分。除了缺乏标准之外,没有被广泛接受的float8 类型的主要原因之一是它实际上不是很有用。

    浮点入门

    在标准表示法中,float[n] 数据类型使用n 位存储在内存中。这意味着最多只能表示 2^n 唯一值。在 IEEE 754 中,少数这些可能的值,例如 nan,并不是这样的偶数。这意味着所有浮点表示(即使你去float256)在它们能够表示的有理数集中都有间隙,如果你试图在这个间隙中获得一个数字的表示,它们会四舍五入到最接近的值。一般n越高,这些差距越小。

    如果您使用struct 包来获取一些float32 数字的二进制表示,您可以看到实际的差距。一开始有点吃惊,但在整数空间中就有 32 的差距:

    import struct
    
    billion_as_float32 = struct.pack('f', 1000000000 + i)
    for i in range(32):
        billion_as_float32 == struct.pack('f', 1000000001 + i) // True
    

    通常,浮点最擅长仅跟踪最高有效位,因此如果您的数字具有相同的比例,则可以保留重要的差异。浮点标准通常仅在它们在基数和指数之间分配可用位的方式上有所不同。例如,IEEE 754 float32 使用 24 位作为基数,8 位作为指数。

    返回float8

    根据上述逻辑,float8 值只能采用 256 个不同的值,无论您在基数和指数之间拆分位时多么聪明。除非你热衷于将数字四舍五入到接近零的 256 个任意数字之一,否则它可能更有效地跟踪 int8 中的 256 种可能性。

    例如,如果您想以粗略的精度跟踪一个非常小的范围,您可以将您想要的范围划分为 256 个点,然后存储您的数字最接近 256 个点中的哪一个。如果您想变得真正花哨,则可以根据对您最重要的内容,将值的非线性分布集中在中心或边缘。

    其他任何人(甚至以后您自己)需要这种确切方案的可能性非常很小,并且大多数情况下,您为使用 float16float32 太小而无法产生有意义的差异。因此...几乎没有人会费心编写float8 实现。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-21
      • 2019-10-24
      • 2013-02-26
      • 2018-11-30
      • 1970-01-01
      • 2018-03-24
      • 1970-01-01
      相关资源
      最近更新 更多