Refactor ParticleAttrib::scatter() - #585
aaadelmann wants to merge 9 commits into
Conversation
…in scatter” decision is made on the host before launching the Kokkos kernel.
**Purpose**
The old code did this inside every particle iteration:
```cpp
size_t mapped_idx = useHashView ? hash_array(idx) : idx;
```
`useHashView` is a runtime boolean captured into the kernel. That means every particle pays for a branch, and the kernel body always mentions `hash_array(idx)` even when no hash view is used.
The new code splits this into two template instantiations:
```cpp
scatterImpl<true>(...)
scatterImpl<false>(...)
```
Inside the kernel it becomes:
```cpp
size_t mapped_idx = idx;
if constexpr (UseHashView) {
mapped_idx = hash_array(idx);
}
```
So for the non-hashed path, the hash-array access is compiled out entirely.
**Secondary Purpose**
The change also moves the hash extent check out of the kernel implementation wrapper and normalizes the type:
```cpp
const auto hashExtent = static_cast<decltype(iteration_policy.end())>(hash_array.extent(0));
```
This avoids the Kokkos 5.2 OpenMP/GCC signed/unsigned warning from comparing `iteration_policy.end()` with `hash_array.extent(0)` directly.
**Behavioral Impact**
The public scatter API does not change.
Expected behavior stays the same:
- no hash array: `mapped_idx = idx`
- hash array present: `mapped_idx = hash_array(idx)`
- hash array too small for the requested iteration policy: abort with the existing diagnostic
This is mainly a compile-time dispatch/performance/cleanup change:
- removes one per-particle runtime branch
- avoids compiling hash access into the non-hash kernel
- fixes the signed/unsigned comparison warning
- keeps the existing bounds check and scatter semantics
** ToDo
- Test on GPUs, on CPU (MAC) no improvement
- The current cast is probably fine for realistic particle counts, but a more defensive version could use a named policy index type and possibly check representability before casting. For IPPL’s current usage, this is not a practicaxl concern.
Port the CUDA-safe hashed scatter implementation from fixissue/#415 onto the hashed-scatter branch. Move the kernel-bearing scatter implementation out of the private ParticleAttrib member scope and into a namespace-scope detail helper. CUDA extended host-device lambdas cannot be enclosed by private or protected class member functions, so keeping the KOKKOS_LAMBDA inside the former private scatterImpl caused GH200/NVCC builds to fail. Keep ParticleAttrib::scatter as the public host-side dispatcher. It validates the optional hash view and selects the hashed or non-hashed implementation via: particleAttribScatterImpl<true> particleAttribScatterImpl<false> Route mapped-index selection through a small KOKKOS_INLINE_FUNCTION helper so the hash lookup is compiled only for the hashed instantiation and is not first captured inside an if constexpr context. Also allow CIC scatter to accept a value type that differs from the field value type. This enables mixed-value scatter such as: ParticleAttrib<float> -> Field<double> by casting the scattered value once to the field view value type before accumulating into the grid. Add regression coverage for: - plain ParticleAttrib<float> -> Field<double> scatter - hashed ParticleAttrib<float> -> Field<double> scatter - dimensions 1, 2, and 3 through GatherScatterTest Tested locally with: cmake --build build-fixissue-415-merge --target GatherScatterTest -j 8 ctest --test-dir build-fixissue-415-merge --output-on-failure -R '^GatherScatterTest$' mpirun -np 2 build-fixissue-415-merge/unit_tests/Particle/GatherScatterTest --gtest_filter='*MixedValueType*' mpirun -np 4 build-fixissue-415-merge/unit_tests/Particle/GatherScatterTest --gtest_filter='*MixedValueType*' All tests passed.
|
That's actually a nice solution: you now use a template to decide which scatter to use, allowing the compiler to optimize the ternary operation. However, "on CPU (MAC) no improvement" is to be expected: one conditional statement is (in my opinion) negligible compared to the scatter kernel itself. It's even possible that right now it uses branch predictions, which would leading to no performance difference at all. When I implemented it, I didn't want code duplication or change templates inside IPPL and settled for a "non-divergent" ternary. But this is for sure the best solution. If you want, I can test this branch against OPALX. |
|
I am now evaluating the performance on LUMI, so no need to OPALX at the moment, thanks Alex ! |
|
I would not expect this to make a big difference because the cost of the branch is really only bad if some of the threads take one path, and some the other - in this case, all take the same path, so it's just an extra compare and jump - however, from an aesthetic point of view, I'd be in favour of templating the kernel as you have done. |
…ib::scatter The refactor moved the kernel implementation into detail::particleAttribScatterImpl, but ParticleAttrib::scatter was left with the timer start and a full set of unused locals (view, mesh, dx, origin, invdx, layout, lDom, nghost). The leftover IpplTimings::startTimer() was never stopped, so the scatter timer was double-started and only stopped once inside the detail helper, corrupting timing data. Remove the unused setup code and leave only the hash-view validation and dispatch.
With Val != T (e.g. float attribute -> double field), the default Val val = T(1) forces an implicit conversion from the mesh/weight type T to the scattered value type Val. Use Val(1) so the default is constructed in the value type directly, consistent with the mixed-value scatter design.
Rename new public member variables Q1/Q2 to QFloat_m/QDouble_m and the new local Total_charge_field to totalChargeField, per AGENTS.md naming rules.
Rename the new public member QFloat to QFloat_m and the new local Total_charge_field to totalChargeField, per AGENTS.md naming rules.
Refactor ParticleAttrib::scatter() so the “hashed scatter” vs “plain scatter” decision is made on the host before launching the Kokkos kernel.
Purpose
The old code did this inside every particle iteration:
size_t mapped_idx = useHashView ? hash_array(idx) : idx;useHashViewis a runtime boolean captured into the kernel. That means every particle pays for a branch, and the kernel body always mentionshash_array(idx)even when no hash view is used.The new code splits this into two template instantiations:
Inside the kernel it becomes:
So for the non-hashed path, the hash-array access is compiled out entirely.
Secondary Purpose
The change also moves the hash extent check out of the kernel implementation wrapper and normalizes the type:
This avoids the Kokkos 5.2 OpenMP/GCC signed/unsigned warning from comparing
iteration_policy.end()withhash_array.extent(0)directly.Behavioral Impact
The public scatter API does not change.
Expected behavior stays the same:
mapped_idx = idxmapped_idx = hash_array(idx)This is mainly a compile-time dispatch/performance/cleanup change:
** ToDo
Test on GPUs, on CPU (MAC) no improvement
The current cast is probably fine for realistic particle counts, but a more defensive version could use a named policy index type and possibly check representability before casting. For IPPL’s current usage, this is not a practicaxl concern.