基准测试移动 GPU 中的浮点精度 - 第 3 部分

原文Benchmarking floating point precision in mobile GPUs - Part III


投稿人:Tom Olson2013  8  29 

 

欢迎阅读我撰写的 GPU 浮点质量系列博文的第 3 部分。此系列的灵感源自Stuart Russell  Youi Labs™ 所做的有趣工作,旨在探索各种移动 GPU行为的差异。在 1 部分中,我介绍了浮点格式,分析了 Stuart 的测试着色器。我们使用它来了解 GPU 片段着色器的浮点精度具有的位数。在 2 部分中,我们探索了浮点取整的各种方式,并使用其结果了解哪些 GPU 采用简单的方式(向零取整),哪些使用更加准确的最近偶数取整。在这最后一篇博文中,我们将探索浮点的另一个古怪之处 - 零附近的孔洞 – 并了解哪些移动 GPU 的设计能避免掉入其中。

 

此系列博文的一个重要前提是使用浮点算法是颇为棘手的。浮点数字的行为与你在学校中所学的数字大体相似,但不完全相同。如果忽略其区别,您的代码大部分时间都能正常运作,但也会经常给您带来问题。如果将浮点用于任何重要的事宜,您必须更加细致地了解其运作原理,细致程度要比大多数 - 我该怎么表达呢?– 大多数正常人所希望的更高。所以,我们的了解过程首先从零洞 (zero hole) 是什么开始,再谈谈 IEEE-754 规范如何应对。然后,我们将编写一个可以将其视觉化的片段着色器。

 

 

比您真正希望的还要细致,第 3 部分

在本系列博文中,我们通篇采用一种类似于 IEEE-754 binary32(也是大多数人在说浮点时所指的意思)的通用浮点格式。该格式使用一个符号位n,一个八位指数 E,以及一个二十四位有效数 (1.sss...) 描述数字。此类数字的值为

 

(-1)n × 2E × 1.sssssssssssssssssssssss

 

其中,E 的范围(逻辑上)是 -126  +127。有效数是定点二进制数字,包含 23 位小数精度,在 1 的位置上具有一个隐式“1”位。在上两篇博文中,我们探讨了将使用这一格式的两个数字相加时会出现的情况,尤其是在两个数字大小有差异时。今天,我们将讨论更加基础的东西:这一格式可以表示的值集合是什么?

 

我们首先从明显的开始:对于 E 的每一个值,有一组 223 个不同的正值可供数字取用。每个集合中的数字在实际数字线上均匀排列,间距为 2(E-23)。所以 E 越来越小,数字之间的距离越来越短,直到 E 到达其最小值 -126。(顺便提一下,这几乎是浮点数字的定义属性:它们所带来的量化误差大致与数字大小成比例。这意味着误差在百分比上基本上是常量,这的确很棒。至少对我而言是这样。)

 

然而,如果您留心(且已经不在玩笑)的话,可能已经注意到我们到目前所解释的格式中有个奇怪的地方:它无法表示零这个数字!(2E 始终大于零,所以有效数始终在 1  2 之间,结果绝不会为零。)我们至少从 al-Khwarizmi 年代开始就知道,零是一个非常、非常重要的数字;所以,这可不行。

 

对这个小问题的解决方法也说明了我们数字格式的另一个奇怪特征。指数的范围是 -126  +127,只有 254 个值,但我们的八位指数可以表示 256个值。另外两个在做什么?答案是它们用作表示无法以通常方式表示的值的标志,如零、无穷或一度流行的 NaN (非数字)。例如,我们可以说,当 E 的逻辑值为 -127 时,数字的值为零。

 

目前还不错,但看看我们的结果是什么。随着 E 变小,可表示数字之间的间距稳步变小,直到我们到达可以表示的最小数字:2-126,其为

 

(-1)0 × 2-126 × 1.00000000000000000000000

 

或者,大约 1.175 × 10-38。这是一个非常小的数字,但不是零;实际上,在这个数字和零之间存在着无限数量的实数。我们可以表示的下一个最大数字是

 

(-1)0 × 2-126 × 1.00000000000000000000001

 

请注意,这两个数字之间的距离是 2-149,这要比 2-126 小得。换一种说法:零与我们可表示的最小正数之间的距离,比该数字与下一个最小数字之间的距离大八百万倍。为了帮助视觉化其样貌,我们想象一个非常原始的浮点格式,其有效数只有 4 位小数精度,最小指数为 -4。如果我们标绘可表示值之间的间距,它会是这样:

 

blogentry-107443-033207100 1377791948_thumb[1].png

 

看到与它们上面的数字间距相比,零和可表示的最小正数之间的间隙有多大了吗?这就是零洞 (zero hole)。对于 24 位有效数而言,情况更糟。

 

 

谁在意呢?

没错,零旁边有一个洞。有关系吗?其实,这取决于你要将浮点用于什么;但本文(与 Stuart 的相似)涉及的是您在做一些极端的事情时出现的情况。而结果表明,如果不对此做些什么,情况会变得很奇怪。我们倾向于相信计算机中数字的行为与我们在学校中学习的数字基本相似,当事实并非如此时,我们会心烦不安。作为一个懵懂的少年,有一些我可以信赖的真理曾经让我感到欣慰;例如,给定 A  B 两个数字,如果 A  B 等于零,那么 A 等于 B。可惜的是,使用我们所讨论的格式时,它甚至连大致正确都算不上。

 

正是类似的考量让 IEEE-754 委员会在长期辩论之后要求提供该问题的解决方案:非规范化次正规数字。想法是非常简单。我们已经有一个特殊指数值来表示零。假如在指数具有该值时,我们不使用普通的公式

 

 = (-1)n × 2E × 1.sssssssssssssssssssssss

 

而使用

 

 = (-1)n × 2-126 × 0.sssssssssssssssssssssss

 

对于之前说过的原始四位格式可表示值的集合现在如下所示

 

blogentry-107443-092740200 1377791956_thumb[1].png

 

零洞消失了!可表示值之间的间距不再在到达零时增加,当且仅当 A 等于 B  A  B 等于零,一切都正常了!

 

 

寻找孔洞

当然,填补零洞不是毫无代价的;这也是 IEEE-754 委员会争论的原因。而且,对于一些应用程序,你可以用自己的方式处理零洞。所以,许多特殊用途处理器(如 DSP)不实施非规范算法也就不足为奇了。那么 GPU 呢?我们可否判断哪些 GPU 完全遵循 IEEE标准,哪些又走了捷径?更为重要的是,有没有有趣的实现方法?

 

以下是一个片段着色器,能够执行类似我们需求的任务:

 

// Denormalization detection shader

precision highp float;

uniform vec2 resolution;

uniform float minexp;

uniform float maxexp;

void main( void ) {

float y = (gl_FragCoord.y / resolution.y) * (maxexp - minexp);

float x = (1.0 - (gl_FragCoord.x / resolution.x));

float row = floor(y) + minexp;

for (float c = 0.0; c < row; c = c + 1.0) x = x / 2.0;

for (float c = 0.0; c < row; c = c + 1.0) x = x * 2.0;

gl_FragColor = vec4(vec3(x), 1.0);

if (x == 0.0) gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);

if (fract(y) > 0.9) gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);

}

 

这有什么用?如果你看过上两篇博文中使用的 Stuart Russell 着色器,会有熟悉的感觉。该着色器在屏幕中的每一像素上执行。第一行(变量 y)将图像分割为水平条纹,分别与 minexp  maxexp 范围内的一个指数值对应。第二行(变量 x)计算浓度值,从图像最左侧的接近 1.0(白色)到最右侧的接近 0.0(黑色)呈线性方式改变。第 3 行确定与像素所处条纹对应的指数。

 

乐趣在于第 4 和第 5 行。第 4 行将灰度值除以 2 E,其中 E 是第 3 行中计算的指数。第 5 行再乘以 2 E。在数学世界中,这会将它恢复到原始值。但浮点运作方式并不如此。如果 E 足够大,第 4 行将导致灰度值下溢(变为零),之后第 5 行就无法将它复原。

 

最后三行将值转换为我们可以看到的颜色。通常,我们返回灰度值;但是,如果值下溢到零,我们会返回红色,表示出现了不良状况。最后,我们在各个条纹之间画一条黑色细线,方便我们统计数量和查看所处的条纹。

 

 

全新(非)正规

所以,我们在移动 GPU 上运行此着色器会看到什么?下图 1 显示使用 Vivante GC4000 GPU  Ascend D1 智能手机上的输出,以及使用Imagination SGX 554  iPad 4 上的输出。这里,我对 minexp  maxexp 进行了设置,使指数的范围在 -120  -152 之间,跨越最小的 FP32 指数 -126。请记住,红色区域对应 GPU 无法与零区分的数字。在这些 GPU 上我们发现,只要浮点指数等于或大于 -126,灰度值就可平滑变化。到达 -127 时,该值突然下溢,掉到了零洞中。这些 GPU 不支持次正规值。在它们的眼中,任何小于 2-126 的值都是零。

blogentry-107443-038898400 1377791965_thumb[1].png

 

 

 2 显示使用 ARM Mali™-T604  Nexus 10 平板电脑上的结果。此时,没有出现灰度值向着 2-126 靠近时保持完整的精度再突然掉到零的情形,而是向着 2-126 靠近时保持完整的精度,然后逐渐下溢,每次放弃一点精度,直到值为 2-149Mali-T604 支持次正规数。它可以表示零与 2-126之间的其他八百万个非零值。

 

blogentry-107443-077836900 1377792003_thumb[1].png

 

我们在许多 GPU 上运行这一着色器,发现支持非正规数的情况比较罕见。事实上,据我们了解,Mali Midgard™ 系列是具备此功能的唯一移动GPU 产品。但是,随着 GPU 计算变得越来越重要,我们进入异构计算的世界,GPU 上的计算提供与 CPU 上相同的结果将至关重要。那一天到来时,我们就准备就绪 - 而那一天就在眼前。对于 Mali-T604 和其后继者率先提供现代 GPU 中最高质量的浮点计算,我们感到自豪。

 

 

然后呢?

我们可以对 GPU 中的浮点行为进行许许多多有趣的研究。它们是否遵循 IEEE 754 运算精度要求?它们如何处理 NaN 和无穷?(例如,除以零的结果是什么?)在评估超越函数方面它们有怎样的表现?我们相信 Mali Midgard GPU 能够在此类竞争中取得好成绩;毕竟,它们是唯一能够通过Full Profile OpenCL 苛刻精度要求的移动 GPU。(有多苛刻?提示:拥有标准 C 数学库的桌面 CPU 将遭遇惨痛的失败。)

 

但这些问题要等以后再说了;与精度相关的三篇连续博文已经是够我承受的了。随着 SIGGRAPH 的到来,会发生许多其他趣事尤其是Samsung Electronics 发布全新 Exynos 5 Octa,其搭载速度超快的 Mali-T628 MP6。而且,在我们的工作中有了些令人振奋的新技术,如Sean Ellis 最近博文中所述的前像素终止 (Forward Pixel Kill) 技术。因此,有一天我们会回来探讨精度。但现在,到此为止。感谢大家

 

 

本系列的博文有:

 

 

 

Tom Olson  ARM 图形研究主管。在当过几年乐手(他没谈过这段经历)、多年为卫星设计数字逻辑之后,他获得了博士学位,并成为一名计算机视觉研究人员。大约在 2001 年,他意识到移动设备图形显示的需求浪潮即将到来,因此将自己的研究领域转向图形显示。在工作时间,他经常思考 ARM GPU  2013 年及之后的年份将用于何种用途。在业余时间,他主持 Khronos OpenGL ES 工作组。

posted on 2014-03-29 15:58  JonnyLulu  阅读(368)  评论(0编辑  收藏  举报

导航