为什么没有2字节浮点数,实现是否已经存在?

Why is there no 2-byte float and does an implementation already exist?

本文关键字:是否 存在 实现 字节 浮点数 为什么      更新时间:2023-10-16

假设我真的很紧张,想要一个更小的范围(类似于short vs int)。Shader语言已经为具有一半精度的浮点类型支持half(不仅仅是来回转换值在-1和1之间,也就是说,返回像这样的浮点数:shortComingIn / maxRangeOfShort)。是否有一个已经存在的2字节浮点数的实现?

我也有兴趣知道为什么没有2字节浮点数的任何(历史?)原因。

TL;DR: 16位浮点数确实存在,并且有各种软件和硬件实现

目前有两种通用的标准16位浮点格式:IEEE-754 binary16和Google的bfloat16。由于它们是标准化的,显然任何了解规范的人都可以编写实现。一些例子:

  • https://github.com/ramenhut/half
  • https://github.com/minhhn2910/cuda-half2
  • https://github.com/tianshilei1992/half_precision
  • https://github.com/acgessler/half_float

或者如果你不想使用它们,你也可以设计一个不同的16位浮点格式并实现它


通常不使用

2字节浮点数,因为即使浮点数的精度也不足以用于正常操作double,除非您受到带宽或缓存大小的限制,否则默认情况下应该始终使用。当在C和类C语言中不带后缀时,浮点字面值也是double。看到

  • 为什么首选double而不是float?
  • 我应该使用double还是float?
  • 什么时候使用float,什么时候使用double

但是小于32位的浮点数确实存在。它们主要用于存储目的,例如在图形中,每像素96位(每通道32位* 3通道)太浪费了,并且将被转换为正常的32位浮点数进行计算(除非在某些特殊硬件上)。OpenGL中存在各种10、11、14位浮点类型。许多HDR格式对每个通道使用16位浮点数,Direct3D 9.0以及一些gpu(如Radeon R300和R420)具有24位浮点数格式。一些8位微控制器(如PIC)的编译器也支持24位浮点数,因为支持32位浮点数的成本太高。8位或更窄的浮点类型不太有用,但由于它们的简单性,它们经常在计算机科学课程中教授。此外,在ARM的指令编码中还使用了一个小浮点数来处理小浮点数。

IEEE 754-2008修订正式增加了16位浮点格式,又名binary16或半精度,具有5位指数和11位尾号

一些编译器支持IEEE-754 binary16,但主要用于转换或向量化操作,而不支持计算(因为它们不够精确)。例如,ARM的工具链有__fp16,它可以在两个变体之间进行选择:IEEE和alternative,这取决于你是否想要更多的范围或NaN/inf表示。GCC和Clang也支持__fp16以及标准化名称_Float16。参见如何在gcc上为x86_64启用__fp16类型

最近由于AI的兴起,另一种称为bfloat16(大脑浮点格式)的格式简单截断IEEE-754 binary32的前16位变得普遍

减少尾数背后的动机来自Google的实验,该实验表明,只要减少尾数,仍然可以表示接近于零的微小值,作为训练过程中微小差异总和的一部分。更小的尾数带来了许多其他优点,如减少乘法器功率和物理硅面积。

  • float32: 242=576 (100%)
  • float16: 112=121 (21%)
  • bfloat16: 82=64 (11%)

许多编译器如GCC和ICC现在也获得了支持bfloat16的能力

bfloat16的更多信息:

  • bfloat16 -硬件数字定义
  • 使用bfloat16与TensorFlow模型
  • 什么是tf。截断的16位浮点数?

在bfloat16不够的情况下还有一个新的19位类型叫做TensorFloat

回复:实现:有人显然已经为C编写了half,这将(当然)在c++中工作:https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/cellperformance-snippets/half.c

Re:为什么float是四个字节:可能是因为低于这个,它们的精度是如此有限。在IEEE-754中,"half"&;只有11位有效精度,产生大约3.311个十进制数字的精度(相比之下,24位的单位产生6到9个十进制数字的精度,53位的双位产生15到17个十进制数字的精度)。

如果内存不足,是否考虑过放弃float的概念?浮点数仅仅为了保存小数点的位置就占用了很多位。你可以解决这个问题,如果你知道在哪里需要小数点,比如你想保存一个美元值,你可以保存为美分:

uint16_t cash = 50000;
std::cout << "Cash: $" << (cash / 100) << "." << ((cash % 100) < 10 ? "0" : "") << (cash % 100) << std::endl;

当然,只有当您可以预先确定小数点的位置时,这才是一种选择。但如果可以的话,总是更喜欢它,因为这也加快了所有的计算速度!

是IEEE 754的16位浮点数标准。

这是一种新的格式,在2008年基于2002年发布的GPU进行了标准化。

要比Kiralein更进一步,我们可以定义一个范围,并允许a short的整数值在该范围内表示相等的除数,如果跨越零,则具有一定的对称性:

short mappedval = (short)(val/range);

这些整数版本和使用半精度浮点数的差异:

  1. 整数在范围内间隔相等,而浮点数在零附近更密集地排列
  2. 使用整数将在CPU中使用整数数学而不是浮点数。这通常更快,因为整数操作更简单。话虽如此,将值映射到不对称范围将需要额外的添加等,以便在最后检索值。
  3. 绝对精度损失更可预测;您知道每个值的误差,因此在给定范围内,可以提前计算出总损失。相反,使用浮点数更容易预测相对误差。
  4. 可能有一小部分操作可以使用值对,特别是位操作,通过将两个short打包到int中。这可以将所需的循环次数减半(如果短操作涉及转换为int,则可以减少更多),并保持32位的宽度。这只是位切片的稀释版本,其中并行处理32位,用于加密。

如果你的CPU支持F16C,那么你可以得到一些东西,并运行得相当快,如:

// needs to be compiled with -mf16c enabled
#include <immintrin.h>
#include <cstdint>
struct float16
{
private:
  uint16_t _value;
public:
  inline float16() : _value(0) {}
  inline float16(const float16&) = default;
  inline float16(float16&&) = default;
  inline float16(const float f) : _value(_cvtss_sh(f, _MM_FROUND_CUR_DIRECTION)) {}
  inline float16& operator = (const float16&) = default;
  inline float16& operator = (float16&&) = default;
  inline float16& operator = (const float f) { _value = _cvtss_sh(f, _MM_FROUND_CUR_DIRECTION); return *this; }
  inline operator float () const 
    { return _cvtsh_ss(_value); }
  inline friend std::istream& operator >> (std::istream& input, float16& h) 
  { 
    float f = 0;
    input >> f;
    h._value = _cvtss_sh(f, _MM_FROUND_CUR_DIRECTION);
    return input;
  }
};

数学仍然使用32位浮点数执行(F16C扩展仅提供16/32位浮点数之间的转换-不存在使用16位浮点数计算算术的指令)。

在不同的实现中可能有多种类型。stint .h的float等效物似乎是个好主意。根据类型的大小调用(别名?)( float16_t ?)一个4字节的浮点数只是现在,但它可能不会变得更小。像half和long这样的术语随着时间的推移变得毫无意义。对于128位或256位的计算机,它们可能意味着任何东西。

我正在处理图像(1+1+1字节/像素),我想表达每个像素相对于平均值的值。所以是浮点数还是谨慎的定点,但不要是原始数据的4倍。16位浮点数听起来很合适。

这个GCC 7.3不知道"half",可能在c++上下文中。

2字节浮点数在clang C编译器中是可用的,数据类型表示为__fp16

不同的编译器现在支持三种不同的半精度格式:

  • __fp16主要用作存储格式。一旦你对它进行计算,它就会被提升为浮动。__fp16上的计算将给出浮点结果。__fp16具有5位指数和10位尾数。
  • _Float16与__fp16相同,但用作交换和算术格式。对_Float16的计算将得到一个_Float16的结果。
  • __bf16是精度较低的存储格式。它有8位指数和7位尾数。

这三种类型都被ARM架构的编译器支持,现在也被x86处理器的编译器支持。AVX512_FP16指令集扩展将由英特尔即将推出的Golden Cove处理器支持,并由最新的Clang、Gnu和英特尔编译器支持。在支持AVX512_FP16的编译器上,_Float16的向量定义为__m128h、__m256h和__m512h。

引用:https://developer.arm.com/documentation/100067/0612/Other-Compiler-specific-Features/Half-precision-floating-point-data-types

https://clang.llvm.org/docs/LanguageExtensions.html half-precision-floating-point