PropertyField - #331
Conversation
… 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
Codecov Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
`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
left a comment
There was a problem hiding this comment.
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
-
SVectorproperties are silently no longer accepted.create_propertiesis a documented part of the physics interface, and a downstream package returningSVector{NP,Float64}— as Carina does — now dies inside_setup_propertieswith a bareMethodError, becauseVector{SVector{N,T}}matches neitherPropertyField(::Vector{<:Vector{T}})norPropertyField(::Vector{<:Array{T}}). I see the intent in theSVector{0,Float64}() -> zeros(0)default changes, but the transition should either accept anyAbstractVectorand convert, or fail with a message that says what to do. This is the one item I would want addressed before merge. -
values(p.properties)changes meaning without failing. Base has a genericvalues(itr) = itrfallback, so existingzip(values(p.physics), ..., values(p.properties))loops do not error — they silently iterate the flat array's individualFloat64s 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 avalues(::PropertyField)method that errors with a pointer toproperties(field, e, b)would turn a puzzling failure into an obvious one. -
_element_level_propertiesis now dead but still defined atAssemblers.jl:205,212. Ours is the only caller I know of and we are removing it; worth deleting so it does not look supported. -
The branch is behind
main— it forks fromc0fd705and is missing #328, #329 and #330, includingas_matrix_free, which Carina's GPU transfer path calls by name.mainmerges cleanly into it, no conflicts. -
Minor, in
properties:offset,nepes[b]andisblockconstant[b]are loop-invariant inebut 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.
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
NamedTupleNamedTuples.This changed several things.
Parametersnow storesPropertyFieldinstead ofNamedTuples.foreach_blockno longer returns propertiesproperties(props, e, b)method can access the properties for element numberein blockb. This is NOT the global element id but the local block element number.NamedTuples of properties or now you can also passDicts.Parametersare now strongly typed asH1Field{RT, RV, D}which introduced the dimensionDparameter toParametersforeach_blockinterface andpropertiesinterface.@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.