Query about spriv precision in shader within angle vulkan backend

22 views
Skip to first unread message

ChenHz Le

unread,
Aug 30, 2022, 10:01:05 PM8/30/22
to angleproject
Hi,

Recently I encountered a strange problem when using Angle's Vulkan backend on RTX3080 card with the latest driver.
Here is the simplified pixel shader in question:

out vec4 color;

void main() {
    mediump vec4 a = vec4(0.6, 0.6, 0.6, 1.0);
    mediump float m = max(max(a.x, a.y), a.z);

    if (m == a.x || m == a.y || m == a.z) { // should be always true
        color = vec4(0.0, 1.0, 0.0, 1.0); // green color
    } else {
        color = vec4(1.0, 0.0, 0.0, 1.0); // red color
    }
}

In this shader, a constant vec4 'a' is given, and 'm' is the largest component of a.xyz.
By looking at the source, the if check should always be true and the output color should be green.
However, on RTX3080, when runing with vulkan backend, the output is red.

Interestingly, there are several ways to make it right:

1. remove all mediump decorators. Or
2. change a to vec4(0.5, 0.5, 0.5, 1.0). Or
3. turn off the enablePrecisionQualifiers feature in vulkan backend. Or
3. use a different gpu, like GTX1650.

So this issue should related to the shader variable precision handling.
I noticed [[RelaxedPrecision]] decoration is added in the shader spirv to the corresponding variable.
I am not familier with spirv, and my question is:

* I checked the spirv spec and it provides OpTypeFloat to explicitly indicate the bit size of a float,
  why using [[RelaxedPrecision]] instead of OpTypeFloat. Also I found that [[RelaxedPrecision]]'s documentation
  is a bit hard to follow and seems its behavior is relying on the implementation?

* Given the previous thought, what is the real reason of the previous problem.
  Can we treat it as a driver bug? or is it because of the undefined hehavior of [[RelaxedPrecision]], and we encounter
  a rare but reasonable situation, which means the spriv generated should be adjusted in some way.

* As a cheap workaround of this problem, we can turn off enablePrecisionQualifiers in Angle, removing all
  [[RelaxedPrecision]]. Will this hurt the performance? and if so, by how much? Again I am not familier with spirv and
  its performance, and I hope anyone can give me a general idea that how serious this change will impact performance.


It has been a long way to finally locate this issue and now I am stuck here, any help will be much appreciated. Thank you.

Yours.

Shahbaz Youssefi

unread,
Aug 30, 2022, 11:12:14 PM8/30/22
to angleproject
Hi,

First off, I'd like to direct you to this reading about floating points: https://floating-point-gui.de/ (found it in pdf form at https://www.phys.uconn.edu/~rozman/Courses/P2200_15F/downloads/floating-point-guide-2015-10-15.pdf)

For example, you should know that 0.6 cannot be represented in floating point accurately, while 0.5 can. That should give you a hint why 0.5 works in your example but not 0.6 :)

With that knowledge in hand, here are some answers to your questions.


> * I checked the spirv spec and it provides OpTypeFloat to explicitly indicate the bit size of a float,
>   why using [[RelaxedPrecision]] instead of OpTypeFloat. Also I found that [[RelaxedPrecision]]'s documentation
>   is a bit hard to follow and seems its behavior is relying on the implementation?

RelaxedPrecision is indeed vague. It tells the driver that it is allowed to use less precision when doing calculations with this floating point number (even if the float is declared as 32-bits). In GLSL, that corresponds to mediump. RelaxedPrecision/mediump floats are expected to at least provide as much precision as a half-precision float (https://en.wikipedia.org/wiki/Half-precision_floating-point_format). This is generally called half, or float16.

Now Vulkan/SPIR-V later rectified this ambiguity with VK_KHR_shader_float16_int8, allowing shaders to explicitly ask for a float16 type. ANGLE doesn't yet take advantage of this extension, so it can support as many devices as possible. However, we generally do operate under the assumption that RelaxedPrecision float _is_ indeed float16 on devices that are able to use it.

As to why we use RelaxedPrecision, read on.


> * Given the previous thought, what is the real reason of the previous problem.
>   Can we treat it as a driver bug? or is it because of the undefined hehavior of [[RelaxedPrecision]], and we encounter
>   a rare but reasonable situation, which means the spriv generated should be adjusted in some way.

The reason RelaxedPrecision is affecting the execution of the shader is indeed that the GPU is running the code with different types (float32 vs float16). The failure is neither a driver bug, nor something to be accounted for in SPIR-V generation. If you've read the recommended reading above, you should know by now that similar things happen on the CPU as well. The truth is, float is precise only for certain numbers. For the rest, every number is represented with a small error. As mathematical operations are performed on numbers, that error can accumulate. As a rule of thumb, therefore, you shouldn't test floating point results with ==, but instead with something like abs(diff)<epsilon. Again, this is the same for both CPUs and GPUs.

You might argue that in your very simple case there is no actual mathematics involved, so that the error in representation of 0.6 in `m` vs `a.x` for example should be identical. However, things are not that simple. There are all sorts of optimization tricks in the GPU's ALU as well as the compiler that may result in imprecise handling of floating point values.

As to why this test fails on newer GPUs and not older ones, it could be that the new GPU has faster math that is less precise, or it could be that it can take advantage of float16 more opportunistically than the older GPU could.


> * As a cheap workaround of this problem, we can turn off enablePrecisionQualifiers in Angle, removing all
>   [[RelaxedPrecision]]. Will this hurt the performance? and if so, by how much? Again I am not familier with spirv and
>   its performance, and I hope anyone can give me a general idea that how serious this change will impact performance.

Yes, this will hurt performance. The "how much" depends on the GPU. On mobile GPUs, which traditionally run GLES (which has the concept of precisions), the answer is "a lot". On desktop GPUs, which traditionally run desktop OpenGL (which doesn't have the concept of precision), the answer is "probably not so much on older GPUs, but more on recent ones". It's because newer GPUs do in fact take advantage of float16. Note that "new" could even include GPUs from 10 years ago.

As to what "a lot" means, I could say that some GPUs can do twice the amount of calculation on float16 values compared to float32. So depending on what percentage of your shader is RelaxedPrecision math, you could have anything between nothing to 50% slowdown. Rough estimates, of course.

---

So, what should you do? Definitely keep RelaxedPrecision be. Instead, change your shader to avoid direct comparison of floating point numbers. For example, this change should make the shader work (warning: untested code):

-    if (m == a.x || m == a.y || m == a.z)
+    if (any(lessThan(abs(a.xyz - m), vec3(0.0001))))

Cheers,

ChenHz Le

unread,
Aug 31, 2022, 12:56:25 AM8/31/22
to angleproject
Hi,   ShabbyX
Thank you very much for the prompt reply !  The information you provided really help me a lot to understant the issue :)
Best wishes!
Yours.

Reply all
Reply to author
Forward
0 new messages