Floating-point behavior is currently determined by target defaults, which differ from Rust's and from each other. Vulkan 1.1-1.4 added features that support configuration of float behavior to match Rust's, and I'd like to use those to bring GPU float behavior close to CPU. This will make the rust-gpu Vulkan target viable for scientific computing.
WGSL does not have any float control mechanism yet, so it will have to wait. For CUDA, the defaults already stick close to Rust's semantics, although specific compilers targeting CUDA may perform inconsistent reordering optimizations like automatic parallelization of sums and products.
The scope of behavior that this spans is: algebraic reordering, automatic FMA fusing, rounding, subnormal flushing, and Inf/NaN assumptions.
Here's my proposed solution, which I've implemented in 3 stacked branches:
- Add float control policies, Passing CI
a. This one looks large, but it's mostly tests and diffs, with about 400 lines of actual code changes.
- Carry float controls through linking and vectorization, Passing CI
- Set
rust_math mode as the default for Vulkan, Passing CI
Example use-cases that do not work reliably now, but would work after this update:
- Geometric predicates (incircle test, etc)
- Deliberate accounting for floating-point roundoff is ubiquitous in computational geometry; most meshing software can't run with float reordering enabled
- Full-precision accumulation procedures like Two-Sum and Two-Prod and improved-accuracy methods like Pairwise Sum require rigorously consistent operation ordering and roundoff behavior to function at all
- Compensated arithmetic is required in solvers and integrators for stiff/ill-conditioned systems, for example, in control systems and signal processing
Related ticket with narrower scope: #229
Proposal
Floating-point policies
A per-entrypoint annotation in [rust_math (Vulkan default), fast_math, compat_math] determines float controls flags.
The unannotated default is rust_math only on Vulkan targets. Non-Vulkan targets retain their previous
defaults and reject explicit rust_math and fast_math annotations. There is no automatic fallback.
Usage like #[spirv(compute(threads(1), rust_math))] and so on.
Constraints
- Vulkan requires roundoff behavior and subnormal handling are configured per-entrypoint (similar to CPU environments)
- This means nested kernels inherit the parent kernel's settings, even if they aren't the right ones
- Ideally, each kernel would be able to control its own execution requirements, but that's not supported by APIs (and probably not supported by hardware)
- This is somewhat achievable in the same way as in Rust on CPU: use
rust_math strict mode as the blanket setting, and use explicit algebraic operations to opt out locally
- Feature requirements are module-wide
- Must use separate output modules to deploy
compat_math entry points independently of rust_math or fast_math entry points
- Runners must enable
shaderFloatControls2 and support controls for every floating-point width used
- Not all targets support enough/any float controls to match Rust
- SPIR-V>=1.4 supports adequate float controls, but not all downstream targets do
- Vulkan 1.1–1.3 requires
VK_KHR_shader_float_controls2; the interface is core in Vulkan 1.4
- WGSL does not expose any float controls at all
- Upstream extension requires changes to WGSL spec
- WGPU naga does not support float controls even if targeting SPIR-V output
- Upstream extension is possible but would not cover all output targets
- Vector operations can only have one set of flags
- When scalar operations being combined into a vector operation have different flags, the stricter set is used
- This avoids either (1) rejecting vectorization of scalar ops with different flags or (2) producing a less correct from vector ops than scalar
- These settings do not guarantee that the hardware has properly implemented IEEE-754
Details
rust_math and fast_math emit control flags that map to the Vulkan FloatControls2 API.
While this is technically a Vulkan-specific pattern, it matches closely to historical CPU flags, and can be expected to map to other float control APIs reasonably well.
rust_math is the Vulkan default. It reflects Rust's strict floating-point semantics.
fast_math is a qualitative environment. Nothing in particular can be said about its validity except that it looks fine sometimes.
compat_math emits no flags, maximizing compatibility but leaving float semantics to be determined by the target's default.
Targets' default behavior varies widely; WGSL's default is even less strict than fast_math, while CUDA is similar to
rust_math, and Vulkan's un-controlled default falls in the middle.
Hardware and firmware introduce further variance in defaults and feature support.
| Behavior |
compat_math entrypoint |
Default / rust_math entrypoint |
fast_math entrypoint |
Explicit Rust algebraic operation (rust_math / fast_math) |
| Algebraic reordering |
Allowed |
Disabled |
Allowed |
Allowed |
| Implicit FMA contraction |
Allowed |
Disabled |
Allowed |
Allowed |
| Reciprocal transformations |
Allowed |
Disabled |
Allowed |
Allowed |
| Signed-zero distinction |
May be ignored |
Preserved |
May be ignored |
May be ignored |
| Assume inputs cannot be NaN or infinity |
Allowed |
No |
No |
No |
| Subnormal arithmetic |
May flush to zero |
Preserve |
Flush to zero |
Inherits entrypoint policy |
| Rounding |
Implementation-defined |
Nearest, ties to even |
Nearest, ties to even |
Inherits entrypoint policy |
Target compatibility
| Target |
Default (no annotation) |
rust_math |
fast_math |
compat_math |
Vulkan 1.4 (spirv-unknown-vulkan1.4) |
Yes, with required device support |
Yes, with required device support |
Yes, with required device support |
Yes |
Vulkan 1.1–1.3 with VK_KHR_shader_float_controls2 |
Yes, with required device support |
Yes, with required device support |
Yes, with required device support |
Yes |
Vulkan 1.0 (spirv-unknown-vulkan1.0) and Vulkan 1.1–1.3 without VK_KHR_shader_float_controls2 |
No |
No |
No |
Yes |
Generic SPIR-V (spirv-unknown-spv*) |
Yes, target defaults |
No |
No |
Yes, target defaults |
OpenGL (spirv-unknown-opengl*) |
Yes, target defaults |
No |
No |
Yes, target defaults |
WGSL via Naga (spirv-unknown-naga-wgsl) |
Yes, WGSL defaults |
No |
No |
Yes, WGSL defaults |
| Vulkan-targeted SPIR-V consumed through wgpu/Naga, including Vulkan output |
No |
No |
No |
Yes |
References
Vulkan floating-point rules.
Vulkan extension requirements
WGSL floating-point rules
Floating-point behavior is currently determined by target defaults, which differ from Rust's and from each other. Vulkan 1.1-1.4 added features that support configuration of float behavior to match Rust's, and I'd like to use those to bring GPU float behavior close to CPU. This will make the rust-gpu Vulkan target viable for scientific computing.
WGSL does not have any float control mechanism yet, so it will have to wait. For CUDA, the defaults already stick close to Rust's semantics, although specific compilers targeting CUDA may perform inconsistent reordering optimizations like automatic parallelization of sums and products.
The scope of behavior that this spans is: algebraic reordering, automatic FMA fusing, rounding, subnormal flushing, and Inf/NaN assumptions.
Here's my proposed solution, which I've implemented in 3 stacked branches:
a. This one looks large, but it's mostly tests and diffs, with about 400 lines of actual code changes.
rust_mathmode as the default for Vulkan, Passing CIExample use-cases that do not work reliably now, but would work after this update:
Related ticket with narrower scope: #229
Proposal
Floating-point policies
A per-entrypoint annotation in [
rust_math(Vulkan default),fast_math,compat_math] determines float controls flags.The unannotated default is
rust_mathonly on Vulkan targets. Non-Vulkan targets retain their previousdefaults and reject explicit
rust_mathandfast_mathannotations. There is no automatic fallback.Usage like
#[spirv(compute(threads(1), rust_math))]and so on.Constraints
rust_mathstrict mode as the blanket setting, and use explicit algebraic operations to opt out locallycompat_mathentry points independently ofrust_mathorfast_mathentry pointsshaderFloatControls2and support controls for every floating-point width usedVK_KHR_shader_float_controls2; the interface is core in Vulkan 1.4Details
rust_mathandfast_mathemit control flags that map to the Vulkan FloatControls2 API.While this is technically a Vulkan-specific pattern, it matches closely to historical CPU flags, and can be expected to map to other float control APIs reasonably well.
rust_mathis the Vulkan default. It reflects Rust's strict floating-point semantics.fast_mathis a qualitative environment. Nothing in particular can be said about its validity except that it looks fine sometimes.compat_mathemits no flags, maximizing compatibility but leaving float semantics to be determined by the target's default.Targets' default behavior varies widely; WGSL's default is even less strict than
fast_math, while CUDA is similar torust_math, and Vulkan's un-controlled default falls in the middle.Hardware and firmware introduce further variance in defaults and feature support.
compat_mathentrypointrust_mathentrypointfast_mathentrypointrust_math/fast_math)Target compatibility
rust_mathfast_mathcompat_mathspirv-unknown-vulkan1.4)VK_KHR_shader_float_controls2spirv-unknown-vulkan1.0) andVulkan 1.1–1.3 without
VK_KHR_shader_float_controls2spirv-unknown-spv*)spirv-unknown-opengl*)spirv-unknown-naga-wgsl)References
Vulkan floating-point rules.
Vulkan extension requirements
WGSL floating-point rules