Skip to content

PropertyField - #331

Merged
lxmota merged 2 commits into
mainfrom
fields/properties
Aug 6, 2026
Merged

PropertyField#331
lxmota merged 2 commits into
mainfrom
fields/properties

Conversation

@cmhamel

@cmhamel cmhamel commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Hooking up PropertyField to be the required interface moving forward. It stores all properties as a flat vector and can allow for some or all of the blocks in the mesh to have element specific properties rather than the properties being constant across the whole block. The indexing behavior that supports this also lives on the GPU as Cu/RocM/Vectors of Ints.

The feature can allow for several things

  • Faster compile times since there's one less NamedTuple
  • Properties all now live in one central memory location making adjoint calculations with AD tools more straightforward
  • Potential for stochastic simulations where block properties are sampled from a distribution either on a single block or all blocks
  • The entire set of properties now completely lives in GPU memory space. No more looping over NamedTuples.

This changed several things.

  • Parameters now stores PropertyField instead of NamedTuples.
  • foreach_block no longer returns properties
  • with kernels the properties(props, e, b) method can access the properties for element number e in block b. This is NOT the global element id but the local block element number.
  • The input API is the same, you can pass NamedTuples of properties or now you can also pass Dicts.
  • Coordinates in Parameters are now strongly typed as H1Field{RT, RV, D} which introduced the dimension D parameter to Parameters
  • Assemblers were updated to use the new foreach_block interface and properties interface.

@lxmota this is a pretty decent change to the assemblers so I'll wait to merge this for a while in case it winds up tanking performance over in Carina.

… It stores all properties as a flat vector and can allow for some or all of the blocks in the mesh to have element specific properties rather than the properties being constant across the whole block. The indexing behavior that supports this also lives on the GPU as Cu/RocM/Vectors of Ints
@cmhamel
cmhamel requested a review from lxmota August 6, 2026 04:12
@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.11111% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.93%. Comparing base (c0fd705) to head (0d3ab53).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
src/Parameters.jl 46.66% 8 Missing ⚠️
src/Fields.jl 97.43% 1 Missing ⚠️
src/Utils.jl 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #331      +/-   ##
==========================================
+ Coverage   67.05%   70.93%   +3.88%     
==========================================
  Files          54       54              
  Lines        6237     6293      +56     
==========================================
+ Hits         4182     4464     +282     
+ Misses       2055     1829     -226     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

`properties(field, e, b)` returns `view(field.data, start:finish)`.  Building
that SubArray inside a device kernel lowers to a dynamic call, so every
assembler that reads properties fails to compile for the GPU.  GPUCompiler does
not surface it as an InvalidIRError either -- it segfaults in check_ir! while
decoding the jl_invoke operand -- so the failure reads as a crash rather than a
diagnostic.

Take the property count from the physics type and return a statically sized
SVector, mirroring the GPU-safe `connectivity(ref_fe, conn_data, e, boffset)`
that takes its node count from the reference element.  The host-side
three-argument accessor is unchanged and still serves callers that slice a
block outside a kernel.

Verified on gfx1102 against Carina's torsion benchmarks: the explicit and
quasi-static GPU paths compile and run again, physics bit-identical, and the
cost relative to FEC main is within 1.5% on CPU and 0.5% on GPU.

Signed-off-by: Alejandro Mota <amota@sandia.gov>

@lxmota lxmota left a comment

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.

Reviewed with a focus on the question you raised — whether this tanks performance in Carina. Short answer: it does not, but the GPU path did not compile at all, so there was no number to report until that was fixed. I pushed the fix to this branch as 42a21a4; details below.

Benchmarks

Ryzen 9 9900X (24 threads) + Radeon RX 7600 (gfx1102), Julia 1.12.6. Three arms: main, this PR as written, and this PR with the accessor fix. Physics is bit-identical in every arm.

Explicit dynamics — torsion, 160k HEX8 / 530k DOF, 800 steps, mean of 2 reps (spread < 1%):

arm CPU, 24 threads vs main GPU vs main
main 22.98 ms/step 6.53 ms/step
this PR as written 23.30 ms/step +1.4% does not compile
this PR + 42a21a4 23.26 ms/step +1.2% 6.56 ms/step +0.5%

Implicit quasi-static — same mesh, Newton + CG/AMG, mean of load steps 2–4:

arm CPU, 24 threads vs main GPU vs main
main 51.09 s/step 48.41 s/step
this PR as written 51.24 s/step +0.3% does not compile
this PR + 42a21a4 51.00 s/step −0.2% 48.24 s/step −0.4%

The implicit path is the sensitive one, since properties is called once per element per CG iteration there (~980 iterations per solve), and it lands inside run-to-run noise. So the flat-vector indirection costs nothing measurable.

The GPU bug, and why CI missed it

properties(field, e, b) returns view(field.data, start:finish). Constructing that SubArray inside a device kernel lowers to a dynamic call, so every assembler that reads properties fails to compile for the GPU. Worse, GPUCompiler does not report it as an InvalidIRError — it segfaults in check_ir! (validation.jl:284, unsafe_pointer_to_objref on the jl_invoke operand it cannot decode), so the failure surfaces as a bare signal 11 with no diagnostic.

I isolated it rather than guessing. Replacing the body with return field.data — wrong values, no SubArray — compiles and runs clean. Removing the @assert false branch alone does not fix it, and neither does @inbounds on the view.

42a21a4 takes the property count from the AbstractPhysics{NF,NP,NS} type parameter and returns a statically sized SVector, mirroring the GPU-safe connectivity(ref_fe, conn_data, e, boffset) that already takes its node count from the reference element. The three-argument host accessor is untouched and still serves callers that slice a block outside a kernel. FEC's full suite passes locally (51,897 tests).

Worth noting: CI_ROCM went green on the broken code. The GPU jobs evidently never assemble with a physics that carries properties on device, so this class of regression is invisible to them. That gap seems worth closing separately — it is the second GPU-only breakage in this area in as many weeks.

Remaining items

  1. SVector properties are silently no longer accepted. create_properties is a documented part of the physics interface, and a downstream package returning SVector{NP,Float64} — as Carina does — now dies inside _setup_properties with a bare MethodError, because Vector{SVector{N,T}} matches neither PropertyField(::Vector{<:Vector{T}}) nor PropertyField(::Vector{<:Array{T}}). I see the intent in the SVector{0,Float64}() -> zeros(0) default changes, but the transition should either accept any AbstractVector and convert, or fail with a message that says what to do. This is the one item I would want addressed before merge.

  2. values(p.properties) changes meaning without failing. Base has a generic values(itr) = itr fallback, so existing zip(values(p.physics), ..., values(p.properties)) loops do not error — they silently iterate the flat array's individual Float64s and then die somewhere downstream with a confusing error. We hit exactly this in Carina at two call sites. Nothing to fix in FEC necessarily, but it belongs in the release notes, and a values(::PropertyField) method that errors with a pointer to properties(field, e, b) would turn a puzzling failure into an obvious one.

  3. _element_level_properties is now dead but still defined at Assemblers.jl:205,212. Ours is the only caller I know of and we are removing it; worth deleting so it does not look supported.

  4. The branch is behind main — it forks from c0fd705 and is missing #328, #329 and #330, including as_matrix_free, which Carina's GPU transfer path calls by name. main merges cleanly into it, no conflicts.

  5. Minor, in properties: offset, nepes[b] and isblockconstant[b] are loop-invariant in e but re-read per element, and the @assert false "Should never happen" sits in the innermost assembly loop. Neither costs anything measurable — the benchmarks above cover it — but the assert is unreachable by construction and could be hoisted to the constructors.

Downstream

For the record, this breaks Carina in four places: parameter construction (item 1), the two values(p.properties) loops (item 2), and two tests asserting keys(params.properties) == block_names. All four are ours to fix and we have the patch ready, so do not hold the PR for them.

Nice change overall — the flat single-buffer layout is clearly the right direction, and element-level properties open up things we want.

@lxmota lxmota closed this Aug 6, 2026
@lxmota lxmota reopened this Aug 6, 2026

@lxmota lxmota left a comment

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.

Looks good to me.

@lxmota
lxmota merged commit 5444cfb into main Aug 6, 2026
@lxmota
lxmota deleted the fields/properties branch August 6, 2026 19:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants