Skip to content

Adhere to Rust floating-point semantics #643

Open

Description

@jlogan03

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:

  1. Add float control policies
  2. Carry float controls through linking and vectorization

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_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 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) 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

Activity

  1. self-assigned this
    on Sep 13, 2026
  2. jlogan03 commented on Sep 13, 2026

    @jlogan03
    Author

    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.

      - GPU: AMD Radeon 760M integrated graphics (PHOENIX)
      - Vulkan driver: RADV, Mesa 25.2.8 (25.2.8-0ubuntu0.24.04.2)
      - Vulkan API: 1.4.318
    
  3. jlogan03 commented on Sep 16, 2026

    @jlogan03
    Author

    @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

  4. LegNeato commented on Sep 21, 2026

    @LegNeato
    Collaborator

    @Firestar99 said he is going to look at this in-depth this week!

  5. Firestar99 commented on Oct 2, 2026

    @Firestar99
    Member

    Sorry for not getting back to you any sooner, we've had glam push 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:

    1. Docs from one of your functions:
    // SPIR-T 0.4 cannot lower execution modes with ID operands. Its current
    // structurization/layout passes do not transform floating-point arithmetic.
    // Carry the entry policy by name across that pipeline, then materialize modes
    // before SPIRV-Tools performs any arithmetic optimization or validation.
    pub(super) fn take_policies(module: &mut Module) -> Vec<(String, ExecutionModel, u32)> {

    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.

    1. I don't like certain extensions or capabilities being required by default, as it forces users to enable them on their Vulkan Device. Which they may not be able to do cause they're on an old android device that hasn't seen a driver update in years, or they're using wgpu and pass in SPIR-V directly, which fails to transpile. What I'd rather see is you opting into Rust float semantics explicitly.

    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.

  6. jlogan03 commented on Oct 4, 2026

    @jlogan03
    Author

    @Firestar99 Thanks for taking a look!

    1. 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.

    2. 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.

  7. jlogan03 commented on Oct 7, 2026

    @jlogan03
    Author

    @Firestar99 I cleaned up that spirt branch and opened a PR, and opened a PR on rspirv to 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 latest spirt API.

    The rust-gpu branches use my spirt and rspirv forks as dependencies for now. I'll swap them back to the upstream once those PRs land.

  8. jlogan03 commented on Oct 18, 2026

    @jlogan03
    Author

    Updated description based on latest upstream (rebased branches on top of eddyb/spirt-0.5.0, so this should follow #684 )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions