Skip to content

Single-precision (fp32) build support#5033

Open
hardik-corintis wants to merge 27 commits into
firedrakeproject:mainfrom
Corintis:fp32-support
Open

Single-precision (fp32) build support#5033
hardik-corintis wants to merge 27 commits into
firedrakeproject:mainfrom
Corintis:fp32-support

Conversation

@hardik-corintis
Copy link
Copy Markdown
Contributor

@hardik-corintis hardik-corintis commented Apr 15, 2026

fixes #3040

Description

Adds single-precision (fp32) build support. Firedrake can now run on a PETSc installation compiled with --with-precision=single. The approach mirrors complex mode: precision is detected at import time from PETSc's build variables and flows through from there.

AI disclosure: Parts of this PR were developed with assistance from Claude (Anthropic). All changes have been reviewed, tested locally, and are fully understood by the author.

Prerequisite

Requires https://gitlab.com/petsc/petsc/-/merge_requests/9272 (petsc4py: handle PETSC_DOUBLE in DMSwarm.getField). Without it, DMSwarm.getField() raises AssertionError on fp32 builds because PETSC_DOUBLE is not mapped to a numpy dtype in the single-precision case where PETSC_REAL != PETSC_DOUBLE.

Changes

scripts/firedrake-configure

  • Adds --arch single / ScalarType.SINGLE; passes --with-precision=single to PETSc configure; excludes fftw and suitesparse (no fp32 support in those libraries)

tsfc/parameters.py, tsfc/loopy.py, tsfc/kernel_interface/common.py, tsfc/ufl_utils.py

  • scalar_type / scalar_type_c derived from PETSc precision at import time; constant initializers cast to the kernel scalar dtype

Core (evaluate.h, locate.c, pointquery_utils.py, pointeval_utils.py, mg/kernels.py)

  • Replace hardcoded double / int with PetscReal / PetscInt in generated C code
  • Convergence epsilon in point query tightened to 1e-6 in fp32 mode vs 1e-12 in fp64, to stay within single-precision range

firedrake/mesh.py, firedrake/utility_meshes.py

  • Vertex coordinates and reference-cell distances use PETSc.RealType; physical coordinate arrays for rtree and DMSwarmPIC_coor remain float64 (required by the rtree C API and PETSc swarm internals)

firedrake/function.py

  • Point evaluation coerces coordinates to float64 regardless of ScalarType, for geometric robustness in cell location

firedrake/assemble.py, firedrake/functionspaceimpl.py, pyop2/codegen/builder.py

  • Replace dtype=int with dtype=IntType in numpy.prod and array allocation calls

firedrake/utils.py

  • Adds single_mode boolean flag (mirrors complex_mode)

Tests

  • Adds @pytest.mark.skipsingle marker for tests incompatible with fp32
  • tests/firedrake/conftest.py: registers the marker and wires it up
  • Skips a small set of tests that require double-precision accuracy (test_locate_cell, test_interpolate_cross_mesh[extrudedcube], test_parallel_high_order_location)

.github/workflows/core.yml

  • Adds single to the CI matrix alongside default and complex

Known limitations

test_parallel_high_order_location is skipped in fp32: high-order cell location in a warped mesh requires double-precision accuracy that fp32 cannot provide at tolerance=0.0001.

@hardik-corintis hardik-corintis marked this pull request as draft April 15, 2026 14:13
@connorjward
Copy link
Copy Markdown
Contributor

Thanks for this.

adds --arch single

What about single+complex? Isn't that a valid configuration?

PETSc version bump (v3.24.5 → v3.25.0)

This isn't necessary. That's all going to be taken care of when I release the next major version in the next 24 hours.

Fix needed upstream in petsc4py: if ctype == PETSC_DOUBLE: typenum = NPY_DOUBLE in petsc4py/PETSc/DMSwarm.pyx.

Can you get this fixed upstream? Clearly Claude already knows what to do.

@hardik-corintis hardik-corintis force-pushed the fp32-support branch 2 times, most recently from 0dc7472 to dd517a9 Compare May 9, 2026 23:10
@hardik-corintis hardik-corintis marked this pull request as ready for review May 10, 2026 00:18
@hardik-corintis
Copy link
Copy Markdown
Contributor Author

Thanks for this.

adds --arch single

What about single+complex? Isn't that a valid configuration?

Added ScalarType.SINGLE_COMPLEX (--arch single-complex) to firedrake-configure for all four platform targets: single_mode (from PETSC_PRECISION) and complex_mode (from PETSC_SCALAR) are detected independently, and ScalarType already resolves to numpy.complex64 for a single+complex build.

PETSc version bump (v3.24.5 → v3.25.0)

This isn't necessary. That's all going to be taken care of when I release the next major version in the next 24 hours.

Removed from the PR description — thanks.

Fix needed upstream in petsc4py: if ctype == PETSC_DOUBLE: typenum = NPY_DOUBLE in petsc4py/PETSc/DMSwarm.pyx.

Can you get this fixed upstream? Clearly Claude already knows what to do.

Opened a PR here: https://gitlab.com/petsc/petsc/-/merge_requests/9272

Copy link
Copy Markdown
Contributor

@connorjward connorjward left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just looked at the CI/install related side of this (at a glance everything else seems pretty good).

We just have to be careful about adding new test builds to Firedrake. I will try and figure out a solution soon.

Comment thread .github/workflows/core.yml Outdated
Comment thread scripts/firedrake-configure Outdated
@connorjward
Copy link
Copy Markdown
Contributor

We just have to be careful about adding new test builds to Firedrake. I will try and figure out a solution soon.

I have a solution for this in #5117 (merged). Once we update main (#5134) then you can use it in this pull request. You basically need to follow the same procedure that we have for testing complex or CUDA builds.

- mesh.py: clarify why PETSc.RealType is correct for plex_from_cell_list
- mg/kernels.py: fix to_reference_coords_kernel signature (PetscScalar *X -> %(RealType)s *X)
  and add RealType to the template substitution dict
- evaluate.h: comment explaining why int/double is intentional for evaluate()
- test_stokes_mini.py: replace fp32 solver workaround with @pytest.mark.skipsingle;
  fieldsplit path needs a dedicated fp32 test
- conftest.py: remove trailing blank line
Comment thread .github/workflows/push.yml Outdated
@connorjward connorjward added the ci:single Run the test suite in single precision label May 21, 2026
Co-authored-by: Connor Ward <c.ward20@imperial.ac.uk>
Copy link
Copy Markdown
Contributor

@connorjward connorjward left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I think this is good - thank you!

We have to think about testing this. I don't mind having partial test coverage and skipping a lot of things provided that it is clearly recorded that that is only happening because we haven't gotten around to fixing things. For example an issue discussing this should definitely be opened. It might even be a "good first issue".


@pytest.mark.skipcomplex
@pytest.mark.parallel([1, 2])
@pytest.mark.skipsingle # VertexOnlyMesh point location has fp32 precision issues
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still true? You look to have done some work on point location

assert errornorm(w, wcheck) < tol


@pytest.mark.skipsingle # asserts ksp.its == 1 (exact algebraic inverse); not achievable in fp32
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JHopeCollins is there a way we can run this test in single precision? What is the right way to loosen things?

"unitsquare_from_high_order",
"unitsquare_to_high_order",
"extrudedcube",
"extrudedcube", # petsc/petsc!9272 fixes fp32 DMSwarm.getField; re-add skipsingle if that MR is not in the shipped PETSc
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this comment go? Has the PETSc MR been merged?

return errornorm(uexact, u, degree_rise=0), errornorm(pexact, p, degree_rise=0)


@pytest.mark.skipsingle # Schur complement fieldsplit does not converge in fp32; tracked in https://github.com/firedrakeproject/firedrake/pull/5033
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does it mean "tracked in 5033", that's this PR

from firedrake import *
import pytest

pytestmark = pytest.mark.skipsingle
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be helpful wherever we have this to give a reason for the skip. I don't want confusion between "this is genuinely impossible" (e.g. skipnogpu) and "TODO: this just hasn't been worked out".

Comment thread tsfc/loopy.py
if isinstance(temp, gem.Constant):
data.append(lp.TemporaryVariable(name, shape=temp.shape, dtype=dtype, initializer=temp.array, address_space=lp.AddressSpace.LOCAL, read_only=True))
# loopy raises if initializer.dtype != declared dtype (e.g. float64 GEM constant in fp32 build).
initializer = temp.array.astype(dtype) if temp.array.dtype != dtype else temp.array
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like we should really track down where we're inserting float64s and make them the right scalar/real type instead

in single- and double-precision builds. The threshold is loose enough that
discretization error dominates round-off for both fp32 and fp64."""
err = helmholtz(4)[0] # 16x16 mesh, degree 2 — expected L2 error ~2e-4
assert float(err) < 1e-2
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this new test? What is it testing that the other tests aren't?

Comment on lines +1571 to +1572
maintainer="the Firedrake team",
contact="on Slack",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not thrilled about taking on the maintenance burden of these additional configurations given that we have never tested them. We're happy to maintain previous Ubuntus because we used to maintain them. The idea behind COMMUNITY_ARCHS is that we could make these other people's responsibilities - like yours or Corintis'.

Comment thread firedrake/mesh.py
return cache[tolerance]
except KeyError:
IntTypeC = as_cstr(IntType)
RealTypeC = RealType_c
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems pretty redundant

Comment thread firedrake/mesh.py
self.tolerance = tolerance
xs = np.asarray(xs, dtype=utils.ScalarType)
# Physical coordinates: always float64 (libspatialindex requires double).
xs = np.asarray(xs, dtype=np.float64)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to confuse people in future. Can you put comments in some of the places where we end up seeing double when you would otherwise expect to see RealType. E.g. above

                {IntTypeC} locator(struct Function *f, double *x, {RealTypeC} *X, {RealTypeC} *ref_cell_dists_l1, {IntTypeC} *cells, {IntTypeC} npoints, size_t ncells_ignore, {IntTypeC}* cells_ignore)

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

Labels

ci:single Run the test suite in single precision

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support precisions other than double

2 participants