原文地址:Benchmarking floating point precision in mobile GPUs - Part III
原作者:tomolson
欢迎阅读我撰写的 GPU 浮点质量系列博文的第 3 部分。此系列的灵感源自 Stuart Russell 在 Youi Labs™ 所做的一些有趣工作,旨在探索各种移动 GPU 行为的差异。在第 1 部分中,我介绍了浮点格式,分析了 Stuart 的测试着色器。我们使用它来了解 GPU 片段着色器的浮点精度具有的位数。在第 2 部分中,我们探索了浮点取整的各种方式,并使用其结果了解哪些 GPU 采用简单的方式(向零取整),哪些使用更加准确的最近偶数取整。在这最后一篇博文中,我们将探索浮点的另一个古怪之处 - 零附近的孔洞 – 并了解哪些移动 GPU 的设计能避免掉入其中。
此系列博文的一个重要前提是浮点算法使用起来是颇为棘手的。浮点数字的行为与你在学校中所学的数字大体相似,但不完全相同。如果忽略其区别,你的代码大部分时间都能正常运作,但也会经常给你带来问题。如果将浮点用于任何重要的事宜,你必须更加细致地了解其运作原理,细致程度要比大多数 - 我该怎么表达呢?– 大多数正常人所希望的更高。所以,我们的了解过程首先从零洞 (zero hole) 是什么开始,再谈 IEEE-754 规范如何应对。然后,我们将编写一个可以将其视觉化的片段着色器。
在本系列博文中,我们通篇采用一种类似于 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。如果我们标绘可表示值之间的间距,它会是这样:
看到与它们上面的数字间距相比,零和可表示的最小正数之间的间隙有多大了吗?那就是零洞。对于 24 位有效数而言,情况更糟。
没错,零旁边有一个洞。有关系吗?其实,这取决于你要将浮点用于什么;但本文(与 Stuart 的相似)涉及的是你在做一些极端的事情时出现的情况。而结果表明,如果不对此做些什么,情况会变得很奇怪。我们倾向于相信计算机中数字的行为与我们在学校中学习的数字基本相似,当事实并非如此时,我们会心烦不安。作为一个懵懂的少年,有一些我可以信赖的真理曾经让我感到欣慰;例如,给定 A 和 B 两个数字,如果 A 减 B 等于零,那么 A 等于 B。可惜的是,使用我们所讨论的格式时,它甚至连大致正确都算不上。
正是类似的考量让 IEEE-754 委员会在长期辩论之后要求提供该问题的解决方案:非规范化或次正规数值。想法是非常简单。我们已经有一个特殊指数值来表示零。假如在指数具有该值时,我们不使用普通的公式
value = (-1)n × 2E × 1.sssssssssssssssssssssss
而使用
value = (-1)n × 2-126 × 0.sssssssssssssssssssssss
对于之前说过的原始四位格式,可表示值的集合现在如下所示:
零洞消失了!可表示值之间的间距不再在到达零时增加,当且仅当 A 等于 B 时 A 减 B 等于零,一切都正常了!
当然,填补零洞不是毫无代价的;这也是 IEEE-754 委员会争论的原因。而且,对于一些应用程序,你可以用自己的方式处理零洞。所以,许多特殊用途处理器(如 DSP)不实施非规范算法也就不足为奇了。那么 GPU 呢?我们可否判断哪些 GPU 完全遵循 IEEE标准,哪些又走了捷径?更为重要的是,有没有有趣的实现方法?
以下是一个片段着色器,能够执行类似我们需求的任务:
// Denormalization detection shaderprecision highp float;uniform vec2 resolution;uniform float minexp;uniform float maxexp;void main( void ) {
所以,我们在移动 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 的值都是零。
图 2 显示使用 Mali™-T604 的 Nexus 10 平板电脑上的结果。此时,没有出现灰度值向着 2-126 靠近时保持完整的精度再突然掉到零的情形,而是向着 2-126 靠近时保持完整的精度,然后逐渐下溢,每次放弃一点精度,直到值为 2-149。Mali-T604 支持次正规数。它可以表示零与 2-126 之间的其他八百万个非零值。
我们在许多 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 发布全新 Exynos 5 Octa,其搭载速度超快的 Mali-T628 MP6。而且,在我们的工作中有了些令人振奋的新技术,如 Sean Ellis 最近的博文中所述的前像素终止 (Forward Pixel Kill) 技术。因此,有一天我们会回来探讨精度。但现在,到此为止。感谢大家…
本系列的博文有:
基准测试移动 GPU 中的浮点精度
基准测试移动 GPU 中的浮点精度 - 第2部分