Repository navigation
Adhere to Rust floating-point semantics #643
Description
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 2 stacked branches:
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, fast_math, compat_math] determines float controls flags.
All targets maintain un-annotated defaults (compat_math) for maximum compatibility.
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_mathstrict 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_mathentry points independently ofrust_mathorfast_mathentry points
- Must use separate output modules to deploy
- Runners must enable
shaderFloatControls2and 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 result from vector ops than from scalar ops
- These settings do not guarantee that the hardware has properly implemented IEEE-754 or that the drivers will comply with the flags that have been set
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 | Default / compat_math entrypoint |
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 | Yes |
| 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) andVulkan 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
Activity
jlogan03 commentedon Sep 13, 2026
Also worth noting that while we can label floats as algebraic / ok to reorder, whether or not this optimization actually happens is up to the driver. My laptop setup, for example, produces no reordering that I can identify, regardless of control flags, even for trivial cases like
c = a + b - a. Making algebraic and fast-math float behavior fully consistent with Rust may require pulling the reordering pass upward so that it becomes relatively independent of drivers.jlogan03 commentedon Sep 16, 2026
@Firestar99 Happy to make changes if you have notes, or I can open the PRs if this all looks reasonable. Or I can just leave it here for reference if you'd rather handle float controls later - rspirv2 and some tweaks to spirt would simplify things a bit, and longer-term WGSL spec updates could eliminate some branching and platform differences.
Lmk what you think
LegNeato commentedon Sep 21, 2026
@Firestar99 said he is going to look at this in-depth this week!
Firestar99 commentedon Oct 2, 2026
Sorry for not getting back to you any sooner, we've had
glampush a minor patch that actually broke rust-gpu and I was asked if I can make another release.Here's some unordered thoughts on your patches:
This I assume claude written comment is correct, spirt can't handle the ExecutionModeId Instruction. But we control spirt, it's in the rust-gpu github group, so we can just patch it instead of developing a workaround. I've triggered @eddyb to have a look at adding this, as I'm not the most familiar with that part of the codebase.
I'll have a look at getting a spirt-patched version up and running next week, to get this one merged may take a little longer.
jlogan03 commentedon Oct 4, 2026
@Firestar99 Thanks for taking a look!
Agreed, I actually made a spirt fork and implemented that fix a while back if you'd prefer we do that first and avoid the workaround. There are some upgrades on rspirv2 that would clean up this changeset as well although I'm not sure what the target cutover date from rspirv is.
A reasonable compromise - it would be nice to be able to stick to "Rust semantics at all costs" but perhaps this is too much cost. Easy enough to swap the default behavior to point to compat-math (no flags emitted) mode. I'll be busy for a couple days but will make that update on my fork later in the week.
jlogan03 commentedon Oct 7, 2026
@Firestar99 I cleaned up that
spirtbranch and opened a PR, and opened a PR onrspirvto remove the loader workaround as well.Together, these eliminate a hundred lines or so of workarounds. Keeping the un-annotated mode as the default to prioritize compatibility removed the third branch in the stack entirely. However, since it's now targeting the next release of
spirt, this did drive the addition of another branch at the bottom of the stack to update the rest of rust-gpu for the latestspirtAPI.The rust-gpu branches use my
spirtandrspirvforks as dependencies for now. I'll swap them back to the upstream once those PRs land.jlogan03 commentedon Oct 18, 2026
Updated description based on latest upstream (rebased branches on top of
eddyb/spirt-0.5.0, so this should follow #684 )