Skip to content

RAJA::fornest : Abstraction to switch between collapse, nested loops, and tiling patterns - #2021

Open
artv3 wants to merge 47 commits into
developfrom
artv3/feature/forall_nd
Open

RAJA::fornest : Abstraction to switch between collapse, nested loops, and tiling patterns#2021
artv3 wants to merge 47 commits into
developfrom
artv3/feature/forall_nd

Conversation

@artv3

@artv3 artv3 commented Apr 30, 2026

Copy link
Copy Markdown
Member

I along with @tomstitt work an application where we have been exploring using a 1D GPU index for multi-level loops and have found that may be more performant than using hierarchical parallelism. This PR uses concepts from RAJA to create the RAJA::forall_nd convenience function.

Cases in which this approach is more performant:
it comes up when the threads per block are not high enough to saturate the GPU and using RAJA::forall + mods + divs allows us to increase the threads per block which ends up being more performant.

@artv3
artv3 requested a review from tomstitt April 30, 2026 18:20
@artv3 artv3 added this to the June 2026 Release milestone Apr 30, 2026
@MrBurmark

MrBurmark commented Apr 30, 2026

Copy link
Copy Markdown
Member

I am concerned that people will use this without knowing that its doing expensive div/mod calculations and see that it performs more poorly than a native cuda/hip 2d or 3d kernel. Would it make sense to have policies that allow mapping to 2d or 3d kernels.

@artv3

artv3 commented Apr 30, 2026

Copy link
Copy Markdown
Member Author

I am concerned that people will use this without knowing that its doing expensive div/mod calculations and see that it performs more poorly than a native cuda/hip 2d or 3d kernel. Would it make sense to have policies that allow mapping to 2d or 3d kernels.

I think that can be addressed in the RAJA cookbook or examples explaining when this would be performant. In the case of @tomstitt and I, it comes up when the threads per block are not high enough to saturate the GPU and using RAJA::forall + mods + divs allows us to increase the threads per block which ends up being more performant. For 2D/3D gpu grids there are various ways to do that and perhaps we should direct developers to RAJA::launch or RAJA::kernel?

@tomstitt

tomstitt commented Apr 30, 2026

Copy link
Copy Markdown
Member

I am concerned that people will use this without knowing that its doing expensive div/mod calculations and see that it performs more poorly than a native cuda/hip 2d or 3d kernel. Would it make sense to have policies that allow mapping to 2d or 3d kernels.

I think that can be addressed in the RAJA cookbook or examples explaining when this would be performant. In the case of @tomstitt and I, it comes up when the threads per block are not high enough to saturate the GPU and using RAJA::forall + mods + divs allows us to increase the threads per block which ends up being more performant. For 2D/3D gpu grids there are various ways to do that and perhaps we should direct developers to RAJA::launch or RAJA::kernel?

I think my ideal is an interface where there is a choice of policy. We have 2d/3d kernels on 1d iteration spaces, using mod/div like Arturo said, to expose more parallelism. When we switch some of those to using our "true" 2d/3d grid launcher we lose performance because our block (16x16 , 8x8x8) doesn't map well onto the grid (because we just idle threads). It's of course not always true that our div/mod approach is going to be better, like Jason said, and if we had an easy way to pick we could put both behind our abstraction and correctly dispatch

@artv3

artv3 commented Apr 30, 2026

Copy link
Copy Markdown
Member Author

I am concerned that people will use this without knowing that its doing expensive div/mod calculations and see that it performs more poorly than a native cuda/hip 2d or 3d kernel. Would it make sense to have policies that allow mapping to 2d or 3d kernels.

I think that can be addressed in the RAJA cookbook or examples explaining when this would be performant. In the case of @tomstitt and I, it comes up when the threads per block are not high enough to saturate the GPU and using RAJA::forall + mods + divs allows us to increase the threads per block which ends up being more performant. For 2D/3D gpu grids there are various ways to do that and perhaps we should direct developers to RAJA::launch or RAJA::kernel?

I think my ideal is an interface where there is a choice of policy. We have 2d/3d kernels on 1d iteration spaces, using mod/div like Arturo said, to expose more parallelism. When we switch some of those to using our "true" 2d/3d grid launcher we lose performance because our block (16x16 , 8x8x8) doesn't map well onto the grid (because we just idle threads). It's of course not always true that our div/mod approach is going to be better, like Jason said, and if we had an easy way to pick we could put both behind our abstraction and correctly dispatch

@tomstitt , okay -- Let's get that imagination soaring, and cook up some ideas! #RAJA!

@artv3 artv3 changed the title Draft: Forall_nd Draft: launch_nd Apr 30, 2026
Comment thread examples/launch_nd.cpp Outdated
Comment on lines +108 to +112
RAJA::launch_nd(res, policy, RAJA::segments(cells, comps),
[=] RAJA_HOST_DEVICE(int cell, int comp) {
const int idx = comp + num_comp * cell;
values_ptr[idx] = 1000 * cell + comp;
});

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@tomstitt , @MrBurmark I invite you to take a look at this example, I think this may be what we are looking for.

@MrBurmark

Copy link
Copy Markdown
Member

I am concerned that people will use this without knowing that its doing expensive div/mod calculations and see that it performs more poorly than a native cuda/hip 2d or 3d kernel. Would it make sense to have policies that allow mapping to 2d or 3d kernels.

I think that can be addressed in the RAJA cookbook or examples explaining when this would be performant. In the case of @tomstitt and I, it comes up when the threads per block are not high enough to saturate the GPU and using RAJA::forall + mods + divs allows us to increase the threads per block which ends up being more performant. For 2D/3D gpu grids there are various ways to do that and perhaps we should direct developers to RAJA::launch or RAJA::kernel?

I think my ideal is an interface where there is a choice of policy. We have 2d/3d kernels on 1d iteration spaces, using mod/div like Arturo said, to expose more parallelism. When we switch some of those to using our "true" 2d/3d grid launcher we lose performance because our block (16x16 , 8x8x8) doesn't map well onto the grid (because we just idle threads). It's of course not always true that our div/mod approach is going to be better, like Jason said, and if we had an easy way to pick we could put both behind our abstraction and correctly dispatch

@tomstitt , okay -- Let's get that imagination soaring, and cook up some ideas! #RAJA!

You certainly can use launch or teams to get some level of parallelism and then take those indices and do your own calculations with them. If we had the multiloop abstractions with a variety of policies that could take us a fair amount of the way.

@artv3
artv3 requested a review from a team July 6, 2026 16:11
@artv3
artv3 marked this pull request as ready for review July 6, 2026 16:11
@artv3

artv3 commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

@llnl/raja-core , I received an approval from my project. Opening up the PR for review from the squad.

@artv3 artv3 changed the title Draft: launch_nd launch_nd Jul 6, 2026
@artv3 artv3 changed the title launch_nd Launch_nd: Abstraction to switch between flat and nested loops Jul 6, 2026

@adrienbernede adrienbernede left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@lc-hubcast approve

@rhornung67

Copy link
Copy Markdown
Member

@artv3 I merged in latest develop and ran clang-format so GHA CI check run. Is this ready to be merged when approved?

@artv3

artv3 commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

@artv3 I merged in latest develop and ran clang-format so GHA CI check run. Is this ready to be merged when approved?

Not yet, I think we want @llnl/raja-core to take another look. We had a discussed a while potentially changing the name of the abstraction

@artv3
artv3 requested a review from a team July 14, 2026 16:22
Comment on lines +18 to +19
stay written in logical multi-dimensional indices, but the best GPU mapping is
not known from the source alone.

@MrBurmark MrBurmark Jul 29, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The GPU is part of this, but wouldn't this also be useful for doing things like collapsed openmp loops, or reordered sequential loops?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Underneath we just map to regular nested loops in launch, I think we would have to leave that for when we do the expanded version of this. One thing to explore is transitioning between flat loops to nested loops using threads in a GPU setting. We also would have to figure out the backend on how that would look like for OpenMP

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I suppose it could, I haven't thought about that yet though actually, I think that could be a neat expansion. I think that goes beyond the targeted scope of this PR though, but something to definitely revisit as we expand capabilities. I would be interested in testing it some kernels in RAJAPerf


The interface supports two mapping policy families:

* ``RAJA::fornest_flattened_policy<ExecPolicy, LayoutTag>`` maps the product

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I thought we were going to call this collapse instead of a flatten as this is a multi-dimensional index space being mapped to a 1-D exec space?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Absolutely, we can call it a collapse. Thanks for catching that, I sometimes use the application terminology out of habit.

``RAJA::fornest`` runs a logical 2-D or 3-D loop body through selectable
mappings. It is intended for kernels where the source code should stay written
in logical multi-dimensional indices, but the best GPU mapping is not known from
the source alone.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this dwell on the current support (2d and 3d) or be general and then note that currently only 2d and 3d are supported. The second sentence is a bit awkward and specific to a certain GPU use case when this API is intended to be fairly general for kernels with perfectly nested loops. Maybe something like "Use this for kernels with multi-dimensional indexing that can be expressed sequentially with perfectly nested loops". The "GPU mapping" statement seems like something too specific for the intro.

Comment on lines +21 to +30
The interface supports two mapping policy families:

* ``RAJA::fornest_flattened_policy<ExecPolicy, LayoutTag>`` maps the product
of the logical dimensions to a 1-D iteration space. The ``ExecPolicy`` is a
regular forall-style policy such as ``RAJA::device_exec<256>`` (CUDA/HIP) or
``RAJA::seq_exec`` (host). RAJA performs the linear-to-logical index
reconstruction internally.
* ``RAJA::fornest_mapping_policy<ExecPolicy, LoopPolicies...>`` maps the logical
dimensions directly using one loop policy per dimension. For CUDA/HIP this
allows explicit mapping to global, block, or thread spaces.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Regarding the naming and structure of these policies, are these aliases to a single underlying policy template? I am imagining something like RAJA::fornest_policy<OuterExecPolicy, InnerPolicyList=DefaultList> where in some cases an outer policy can have enough information for a basic case without specifying the inner policies.

Comment thread include/RAJA/pattern/fornest.hpp Outdated
Comment thread include/RAJA/pattern/fornest.hpp Outdated
@artv3 artv3 changed the title RAJA::fornest : Abstraction to switch between flat and nested loops RAJA::fornest : Abstraction to switch between collapse and nested loops Aug 14, 2026
@artv3 artv3 changed the title RAJA::fornest : Abstraction to switch between collapse and nested loops RAJA::fornest : Abstraction to switch between collapse, nested loops, and tiling patterns Aug 18, 2026
@artv3
artv3 marked this pull request as draft August 18, 2026 18:23
@artv3
artv3 marked this pull request as ready for review August 18, 2026 21:10
@artv3

artv3 commented Aug 18, 2026

Copy link
Copy Markdown
Member Author

@MrBurmark @bechols97 , if you have some time can you take a look at the fornest-basic.cpp and dynamic-fornest.cpp ? I want to double check I captured the desired capabilities, then we can start wrapping this up.


The interface supports several policy families:

* ``RAJA::fornest_collapsed_policy<ExecPolicy, LayoutTag>`` maps the product

@MrBurmark MrBurmark Aug 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What does the LayoutTag do? Allow reordering essentially? Does it default to layout_right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also why collapsed with a d policy and not just collapse?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we still have permutation lists that we could imagine taking as the LayoutTag that could be used to reorder the loops?

of the logical dimensions to a 1-D iteration space. The ``ExecPolicy`` is a
regular forall-style policy such as ``RAJA::seq_exec`` or
``RAJA::device_exec<256>``. RAJA performs the linear-to-logical index
reconstruction internally. On host OpenMP builds, ``RAJA::fornest_omp_collapse_policy<...>``

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The fornest_omp_collapse_policy policy is here to allow directly using omp collapse vs doing our own collapse with and using something like omp_parallel_for which can be done via fornest_collapsed_policy<omp_parallel_for, ...>.

segment extents. Device builds can use ``RAJA::device_*`` mapping tags; on
CUDA/HIP these map to CUDA/HIP global, block, and thread indices, and on SYCL
they map to the corresponding work-group and work-item indices where
supported.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

My wouldn't it be more consistent to use policies like RAJA::seq_exec, RAJA::device_exec<256>, etc as the ExecPolicy?

Comment on lines +51 to +52
* ``RAJA::dynamic_fornest<camp::list<...>>(pol, ...)`` selects a policy from a
list at runtime.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
* ``RAJA::dynamic_fornest<camp::list<...>>(pol, ...)`` selects a policy from a
list at runtime.
* ``RAJA::dynamic_fornest<camp::list<...>>(policy_index, ...)`` selects a policy from a
list at runtime.

Would this make it more clear that pol is mean to be an index into the list of policies?

Comment on lines +49 to +50
* ``RAJA::fornest_tiling_policy<...>`` adds per-dimension tiling on top of a
loop nest. Tile sizes may be fixed, runtime-provided, or chosen by RAJA.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
* ``RAJA::fornest_tiling_policy<...>`` adds per-dimension tiling on top of a
loop nest. Tile sizes may be fixed, runtime-provided, or chosen by RAJA.
* ``RAJA::fornest_tiling_policy<ExecPolicy, ...>`` adds per-dimension tiling on top of a
loop nest. Tile sizes may be fixed, runtime-provided, or chosen by RAJA.

Don't we still want to have an ExecPolicy so we can do things like apply launch bounds?

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

#ifndef RAJA_pattern_fornest_HPP
#define RAJA_pattern_fornest_HPP

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we want to split this file up into multiple files.

{

template<typename Policy>
struct policy_dimensionality : std::integral_constant<camp::idx_t, 0>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If we write the policy we can have a static constexpr variable to make it easy to query the number of dimensions instead of writing a separate traits class.

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.

7 participants