mpi-forum / mpi-issues

Tickets for the MPI Forum
http://www.mpi-forum.org/
66 stars 7 forks source link

User defined reduction operations for `mpi_f08` are not natural to write in Fortran #576

Open brandongc opened 2 years ago

brandongc commented 2 years ago

Problem

User defined reduction operations for mpi_f08 are not natural to write in Fortran:

subroutine my_user_function(invec, inoutvec, len, type)   bind(c)
   use, intrinsic :: iso_c_binding, only : c_ptr, c_f_pointer
   use mpi_f08
   type(c_ptr), value :: invec, inoutvec
   integer :: len
   type(MPI_Datatype) :: type
   real, pointer :: invec_r(:), inoutvec_r(:)
   if (type%MPI_VAL == MPI_REAL%MPI_VAL) then
      call c_f_pointer(invec, invec_r, (/ len /))
      call c_f_pointer(inoutvec, inoutvec_r, (/ len /))
      inoutvec_r = invec_r + inoutvec_r
   end if
end subroutine

Proposal

In MPI 5 enable users to write something like

pure subroutine my_user_function(invec, inoutvec)
   real, intent(in), dimension(:) :: invec
   real, intent(inout), dimension(:) :: inoutvec
   inoutvec = invec + inoutvec
end subroutine

Changes to the Text

At minimum change the abstract interface for user specified reductions.

Impact on Implementations

Add a Fortran internal callback trampoline or use ISO_Fortran_binding.h to derive a C representation of the user function?

Impact on Users

Users get to write "normal" Fortran reductions.

jeffhammond commented 1 year ago

I looked at this a bit. I am not sure it is possible to do what you want. Fortran is pretty strict on typing, so the abstract interface for the user function seems like it would have to be:

    abstract interface

    subroutine MPI_User_function_new(invec, inoutvec, len, datatype)
        !use, intrinsic :: iso_c_binding, only : c_ptr
        use mpi_f08, only : MPI_Datatype
        implicit none
        !type(c_ptr), value :: invec, inoutvec
        type(*), dimension(..), intent(in) :: invec
        type(*), dimension(..), intent(inout) :: inoutvec
        integer, intent(in) :: len
        type(MPI_Datatype), intent(in) :: datatype
    end subroutine

    end interface

Then this function becomes the trampoline, and the gymnastics to do the reduction business is no simpler than what you have above (and it seems to be less simple, without changes to Fortran, based on my reading of the standard and testing so far).

Specifically, you'd have to write your reduction with the following signature, and convert the assumed-type arguments as follows, where get_cptr is a trivial C utility function, because it seems that one cannot convert directly to Fortran pointers (I am waiting to hear from J3 if I understand this correctly).

    subroutine X_function(invec, inoutvec, len, datatype)
        use, intrinsic :: iso_c_binding, only : c_ptr, c_f_pointer
        use mpi_f08, only : MPI_Datatype
        use f, only : get_cptr
        implicit none
        type(*), dimension(..), intent(in) :: invec
        type(*), dimension(..), intent(inout) :: inoutvec
        integer, intent(in) :: len
        type(MPI_Datatype), intent(in) :: datatype
        type(c_ptr) :: cpi, cpo
        real, dimension(:), pointer :: fpi, fpo
        call get_cptr(invec,cpi)
        call get_cptr(inoutvec,cpo)
        call c_f_pointer(cpi,fpi,[size(invec)])
        call c_f_pointer(cpo,fpo,[size(inoutvec)])
        ...
    end subroutine

It might be useful for you to discuss this in front of a whiteboard with Bill Long in February (J3 meeting at LBNL), if we don't have a clean resolution by then.

jeffhammond commented 1 year ago

I got some help from Vipul in J3. I think I have a reasonable solution now.

The prototype that looks more like modern Fortran is:

    abstract interface

    subroutine MPI_User_function_f18(invec, inoutvec, len, datatype)
        use mpi_f08, only : MPI_Datatype
        implicit none
        type(*), dimension(..), target, intent(in) :: invec
        type(*), dimension(..), target, intent(inout) :: inoutvec
        integer, intent(in) :: len
        type(MPI_Datatype), intent(in) :: datatype
    end subroutine

    end interface

The user can then write a user-defined reduction that looks something like the following.

    subroutine Y_function(invec, inoutvec, len, datatype)
        use mpi_f08, only : MPI_Datatype
        implicit none
        real, dimension(:), target, intent(in) :: invec
        real, dimension(:), target, intent(inout) :: inoutvec
        integer, intent(in) :: len
        type(MPI_Datatype), intent(in) :: datatype
        inoutvec = inoutvec +  invec
    end subroutine

I am not 100% sure that this is strictly legal Fortran, but it works with ifx.

I need to think of how to proceed from here.

jeffhammond commented 1 year ago

Malcolm said Y_function does not match. However, the following seems to be fully standard-compliant.

One thing we can do here is use SELECT RANK and SELECT TYPE to write a reduction that works for a variety of inputs, whereas with the current interface requires the user to figure this out from len and datatype.

On the other hand, for the cases where the user knows the input type and doesn't need to figure it out via Fortran or MPI datatypes, it's not better.

    subroutine X_function(invec, inoutvec, len, datatype)
        use, intrinsic :: iso_c_binding, only : c_ptr, c_loc, c_f_pointer
        use mpi_f08, only : MPI_Datatype
        implicit none
        type(*), dimension(..), target, intent(in) :: invec
        type(*), dimension(..), target, intent(inout) :: inoutvec
        integer, intent(in) :: len
        type(MPI_Datatype), intent(in) :: datatype
        type(c_ptr) :: cpi, cpo
        integer, dimension(:), pointer :: fpi, fpo
        cpi = c_loc(invec)
        cpo = c_loc(inoutvec)
        call c_f_pointer(cpi,fpi,[size(invec)])
        call c_f_pointer(cpo,fpo,[size(inoutvec)])
        fpo = fpo + fpi
    end subroutine