Neko 1.99.9
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
gs_device_shmem.F90
Go to the documentation of this file.
1! Copyright (c) 2020-2024, The Neko Authors
2! All rights reserved.
3!
4! Redistribution and use in source and binary forms, with or without
5! modification, are permitted provided that the following conditions
6! are met:
7!
8! * Redistributions of source code must retain the above copyright
9! notice, this list of conditions and the following disclaimer.
10!
11! * Redistributions in binary form must reproduce the above
12! copyright notice, this list of conditions and the following
13! disclaimer in the documentation and/or other materials provided
14! with the distribution.
15!
16! * Neither the name of the authors nor the names of its
17! contributors may be used to endorse or promote products derived
18! from this software without specific prior written permission.
19!
20! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21! "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22! LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23! FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24! COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25! INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26! BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27! LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30! ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31! POSSIBILITY OF SUCH DAMAGE.
32!
35 use num_types, only : rp, c_rp
36 use gs_comm, only : gs_comm_t, gs_vec_nc
37 use stack, only : stack_i4_t
38 use htable, only : htable_i4_t
43 use comm, only : pe_size, pe_rank, neko_comm
44 use mpi_f08, only : mpi_allreduce, mpi_alltoall, mpi_integer, mpi_max
45 use utils, only : neko_error
46 use, intrinsic :: iso_c_binding, only : c_sizeof, c_int32_t, c_int64_t, &
47 c_ptr, c_null_ptr, c_size_t, c_associated
48 implicit none
49 private
50
54#if defined(HAVE_CUDA) && defined(HAVE_NVSHMEM)
55 logical, parameter, public :: GS_DEVICE_SHMEM_AVAIL = .true.
56#else
57 logical, parameter, public :: gs_device_shmem_avail = .false.
58#endif
59
61 type, private :: gs_device_shmem_buf_t
62 integer, allocatable :: ndofs(:)
63 integer, allocatable :: offset(:)
64 integer, allocatable :: remote_offset(:)
65 integer :: total
69 integer :: slab_stride
70 type(c_ptr) :: buf_d = c_null_ptr
71 type(c_ptr) :: buf_v_d = c_null_ptr
72 type(c_ptr) :: dof_d = c_null_ptr
73 contains
74 procedure, pass(this) :: init => gs_device_shmem_buf_init
75 procedure, pass(this) :: free => gs_device_shmem_buf_free
77
80 type, public, extends(gs_comm_t) :: gs_device_shmem_t
81 type(gs_device_shmem_buf_t) :: send_buf
82 type(gs_device_shmem_buf_t) :: recv_buf
83 type(c_ptr), allocatable :: stream(:)
84 type(c_ptr), allocatable :: event(:)
89 integer :: iter = 0
96 type(c_ptr) :: done_sig_d = c_null_ptr
97 type(c_ptr) :: ready_sig_d = c_null_ptr
101 type(c_ptr) :: pack_event = c_null_ptr
102 contains
103 procedure, pass(this) :: init => gs_device_shmem_init
104 procedure, pass(this) :: free => gs_device_shmem_free
105 procedure, pass(this) :: nbsend => gs_device_shmem_nbsend
106 procedure, pass(this) :: nbrecv => gs_device_shmem_nbrecv
107 procedure, pass(this) :: nbwait => gs_device_shmem_nbwait
108 procedure, pass(this) :: nbsend_vec => gs_device_shmem_nbsend_vec
109 procedure, pass(this) :: nbrecv_vec => gs_device_shmem_nbrecv_vec
110 procedure, pass(this) :: nbwait_vec => gs_device_shmem_nbwait_vec
111 end type gs_device_shmem_t
112
113
114#if defined (HAVE_CUDA) && defined(HAVE_NVSHMEM)
115
116 interface
117 subroutine cudamalloc_nvshmem(ptr, size) &
118 bind(c, name = 'cudamalloc_nvshmem')
119 use, intrinsic :: iso_c_binding
120 implicit none
121 type(c_ptr) :: ptr
122 integer(c_size_t), value :: size
123 end subroutine cudamalloc_nvshmem
124 end interface
125
126 interface
127 subroutine cudafree_nvshmem(ptr) &
128 bind(c, name = 'cudafree_nvshmem')
129 use, intrinsic :: iso_c_binding
130 implicit none
131 type(c_ptr) :: ptr
132 end subroutine cudafree_nvshmem
133 end interface
134
135 interface
136 subroutine cuda_gs_nvshmem_pack(u_d, buf_d, dof_d, boffset, n, stream) &
137 bind(c, name = 'cuda_gs_nvshmem_pack')
138 use, intrinsic :: iso_c_binding
139 implicit none
140 integer(c_int), value :: boffset, n
141 type(c_ptr), value :: u_d, buf_d, dof_d, stream
142 end subroutine cuda_gs_nvshmem_pack
143 end interface
144
145 interface
146 subroutine cuda_gs_nvshmem_pack_vec(u_d, buf_d, dof_d, boffset, n, nc, &
147 ns, stream) bind(c, name = 'cuda_gs_nvshmem_pack_vec')
148 use, intrinsic :: iso_c_binding
149 implicit none
150 integer(c_int), value :: boffset, n, nc, ns
151 type(c_ptr), value :: u_d, buf_d, dof_d, stream
152 end subroutine cuda_gs_nvshmem_pack_vec
153 end interface
154
155 interface
156 subroutine cuda_gs_push(buf_d, offset, n, stream, dest_rank, rbuf_d, &
157 roffset, iter, done_d, ready_d, mype) &
158 bind(c, name = 'cuda_gs_push')
159 use, intrinsic :: iso_c_binding
160 implicit none
161 integer(c_int), value :: n, offset, dest_rank, roffset, iter, mype
162 type(c_ptr), value :: buf_d, stream, rbuf_d, done_d, ready_d
163 end subroutine cuda_gs_push
164 end interface
165
166 interface
167 subroutine cuda_gs_push_wait(stream, iter, done_d, src_rank) &
168 bind(c, name = 'cuda_gs_push_wait')
169 use, intrinsic :: iso_c_binding
170 implicit none
171 integer(c_int), value :: iter, src_rank
172 type(c_ptr), value :: stream, done_d
173 end subroutine cuda_gs_push_wait
174 end interface
175
176 interface
177 subroutine cuda_gs_post_ready(stream, iter, ready_d, mype, src_rank) &
178 bind(c, name = 'cuda_gs_post_ready')
179 use, intrinsic :: iso_c_binding
180 implicit none
181 integer(c_int), value :: iter, mype, src_rank
182 type(c_ptr), value :: stream, ready_d
183 end subroutine cuda_gs_post_ready
184 end interface
185
186 interface
187 subroutine cuda_gs_unpack(u_d, op, buf_d, dof_d, offset, n, stream) &
188 bind(c, name = 'cuda_gs_unpack')
189 use, intrinsic :: iso_c_binding
190 implicit none
191 integer(c_int), value :: op, offset, n
192 type(c_ptr), value :: u_d, buf_d, dof_d, stream
193 end subroutine cuda_gs_unpack
194 end interface
195
196 interface
197 subroutine cuda_gs_unpack_vec(u_d, op, buf_d, dof_d, offset, n, nc, ns, &
198 stream) bind(c, name = 'cuda_gs_unpack_vec')
199 use, intrinsic :: iso_c_binding
200 implicit none
201 integer(c_int), value :: op, offset, n, nc, ns
202 type(c_ptr), value :: u_d, buf_d, dof_d, stream
203 end subroutine cuda_gs_unpack_vec
204 end interface
205#endif
206
207contains
208
213 subroutine gs_device_shmem_buf_init(this, pe_order, dof_stack, mark_dupes, &
214 nslabs)
215 class(gs_device_shmem_buf_t), intent(inout) :: this
216 integer, allocatable, intent(inout) :: pe_order(:)
217 type(stack_i4_t), allocatable, intent(inout) :: dof_stack(:)
218 logical, intent(in) :: mark_dupes
219 integer, intent(in) :: nslabs
220 integer, allocatable :: dofs(:)
221 integer :: i, j, total, max_total
222 integer(c_size_t) :: sz
223 type(htable_i4_t) :: doftable
224 integer :: dupe, marked, k
225 real(c_rp) :: rp_dummy
226 integer(c_int32_t) :: i4_dummy
227
228 allocate(this%ndofs(size(pe_order)))
229 allocate(this%offset(size(pe_order)))
230 allocate(this%remote_offset(size(pe_order)))
231
232 do i = 1, size(pe_order)
233 this%remote_offset(i) = -1
234 end do
235
236 total = 0
237 do i = 1, size(pe_order)
238 this%ndofs(i) = dof_stack(pe_order(i))%size()
239 this%offset(i) = total
240 total = total + this%ndofs(i)
241 end do
242
243 call mpi_allreduce(total, max_total, 1, mpi_integer, mpi_max, neko_comm)
244
245 this%total = total
246 this%slab_stride = max_total
247
248 sz = c_sizeof(rp_dummy) * nslabs * max_total
249#ifdef HAVE_NVSHMEM
250 call cudamalloc_nvshmem(this%buf_d, sz)
251 ! Fused vector symmetric buffer, sized for up to GS_VEC_NC components.
252 sz = c_sizeof(rp_dummy) * nslabs * gs_vec_nc * max_total
253 call cudamalloc_nvshmem(this%buf_v_d, sz)
254#endif
255
256 sz = c_sizeof(i4_dummy) * total
257 call device_alloc(this%dof_d, sz)
258
259 if (mark_dupes) call doftable%init(2*total)
260 allocate(dofs(total))
261
262 ! Copy from dof_stack into dofs, optionally marking duplicates with doftable
263 marked = 0
264 do i = 1, size(pe_order)
265 ! %array() breaks on cray
266 select type (arr => dof_stack(pe_order(i))%data)
267 type is (integer)
268 do j = 1, this%ndofs(i)
269 k = this%offset(i) + j
270 if (mark_dupes) then
271 if (doftable%get(arr(j), dupe) .eq. 0) then
272 if (dofs(dupe) .gt. 0) then
273 dofs(dupe) = -dofs(dupe)
274 marked = marked + 1
275 end if
276 dofs(k) = -arr(j)
277 marked = marked + 1
278 else
279 call doftable%set(arr(j), k)
280 dofs(k) = arr(j)
281 end if
282 else
283 dofs(k) = arr(j)
284 end if
285 end do
286 end select
287 end do
288
289 call device_memcpy(dofs, this%dof_d, total, host_to_device, sync = .true.)
290
291 deallocate(dofs)
292 call doftable%free()
293
294 end subroutine gs_device_shmem_buf_init
295
297 class(gs_device_shmem_buf_t), intent(inout) :: this
298
299
300 if (allocated(this%ndofs)) deallocate(this%ndofs)
301 if (allocated(this%offset)) deallocate(this%offset)
302 if (allocated(this%remote_offset)) deallocate(this%remote_offset)
303
304#ifdef HAVE_NVSHMEM
305 if (c_associated(this%buf_d)) call cudafree_nvshmem(this%buf_d)
306 if (c_associated(this%buf_v_d)) call cudafree_nvshmem(this%buf_v_d)
307#endif
308 if (c_associated(this%dof_d)) call device_free(this%dof_d)
309
310 end subroutine gs_device_shmem_buf_free
311
313 subroutine gs_device_shmem_init(this, send_pe, recv_pe)
314 class(gs_device_shmem_t), intent(inout) :: this
315 type(stack_i4_t), intent(inout) :: send_pe
316 type(stack_i4_t), intent(inout) :: recv_pe
317 integer :: i, nstrm, ierr
318 integer, allocatable :: local_offsets(:), remote_offsets(:)
319 integer(c_size_t) :: sz
320 integer(c_int64_t) :: i64_dummy
321
322 call this%init_order(send_pe, recv_pe)
323
324 ! The gs schedule registers every sharing peer for both directions, so
325 ! the two lists are identical and index-aligned. Both the per-peer
326 ! stream reuse below (stream(i) serves send_pe(i) and recv_pe(i)) and
327 ! the ungated repack of the parity send slab in nbsend (see there)
328 ! depend on this; fail loudly if a future schedule breaks it.
329 if (size(this%send_pe) .ne. size(this%recv_pe)) then
330 call neko_error('gs_device_shmem requires symmetric peer lists')
331 end if
332 do i = 1, size(this%send_pe)
333 if (this%send_pe(i) .ne. this%recv_pe(i)) then
334 call neko_error('gs_device_shmem requires aligned peer lists')
335 end if
336 end do
337
338 ! Send buffers are parity double-buffered (nslabs = 2), recv buffers
339 ! single (nslabs = 1); see gs_device_shmem_buf_init.
340 call this%send_buf%init(this%send_pe, this%send_dof, .false., 2)
341 call this%recv_buf%init(this%recv_pe, this%recv_dof, .true., 1)
342
343 ! Exchange, for every send peer, the offset in the receiver's recv buffer
344 ! where our slab must land. A single Alltoall keeps the exchange
345 ! deadlock-free for arbitrary, non-uniform peer sets (same rationale as
346 ! the host OpenSHMEM backend); the lazy pairwise exchange this replaces
347 ! indexed send_pe/recv_pe with the same loop index and required uniform,
348 ! index-aligned peer lists on every rank.
349 allocate(local_offsets(0:pe_size - 1))
350 allocate(remote_offsets(0:pe_size - 1))
351 local_offsets = -1
352 do i = 1, size(this%recv_pe)
353 local_offsets(this%recv_pe(i)) = this%recv_buf%offset(i)
354 end do
355 call mpi_alltoall(local_offsets, 1, mpi_integer, &
356 remote_offsets, 1, mpi_integer, neko_comm, ierr)
357 do i = 1, size(this%send_pe)
358 this%send_buf%remote_offset(i) = remote_offsets(this%send_pe(i))
359 end do
360 deallocate(local_offsets)
361 deallocate(remote_offsets)
362
363#if defined(HAVE_HIP) || defined(HAVE_CUDA)
364 ! Create a set of non-blocking streams. The per-peer streams, events and
365 ! notify signals are indexed over both send_pe (pack-and-push) and
366 ! recv_pe (unpack, sync), so size them for the larger of the two peer
367 ! lists.
368 nstrm = max(size(this%send_pe), size(this%recv_pe))
369 allocate(this%stream(nstrm))
370 do i = 1, nstrm
371 call device_stream_create_with_priority(this%stream(i), 1, &
373 end do
374
375 allocate(this%event(nstrm))
376 do i = 1, nstrm
377 call device_event_create(this%event(i), 2)
378 end do
379
380 call device_event_create(this%pack_event, 2)
381
382#ifdef HAVE_NVSHMEM
383 ! Rank-indexed symmetric signal arrays; zero-initialised by
384 ! cudamalloc_nvshmem, so the first round's CMP_GE waits on iter-1 = 0
385 ! pass immediately.
386 sz = c_sizeof(i64_dummy) * pe_size
387 call cudamalloc_nvshmem(this%done_sig_d, sz)
388 call cudamalloc_nvshmem(this%ready_sig_d, sz)
389#endif
390#endif
391
392 this%iter = 0
393 this%vec_supported = .true.
394 ! The vector slabs are part of the symmetric/registered allocation
395 ! made above, which every rank has to take part in, so they cannot
396 ! be deferred to the first fused exchange: a rank with no shared
397 ! dofs never reaches it. See gs_comm_t%vec_ready.
398 this%vec_ready = .true.
399
400 end subroutine gs_device_shmem_init
401
403 subroutine gs_device_shmem_free(this)
404 class(gs_device_shmem_t), intent(inout) :: this
405 integer :: i
406
407 call this%send_buf%free()
408 call this%recv_buf%free()
409
410#ifdef HAVE_NVSHMEM
411 ! Collective frees; every rank allocated both arrays exactly once.
412 if (c_associated(this%done_sig_d)) then
413 call cudafree_nvshmem(this%done_sig_d)
414 this%done_sig_d = c_null_ptr
415 end if
416 if (c_associated(this%ready_sig_d)) then
417 call cudafree_nvshmem(this%ready_sig_d)
418 this%ready_sig_d = c_null_ptr
419 end if
420#endif
421
422 call this%free_order()
423 call this%free_dofs()
424
425#if defined(HAVE_HIP) || defined(HAVE_CUDA)
426 if (allocated(this%stream)) then
427 do i = 1, size(this%stream)
428 call device_stream_destroy(this%stream(i))
429 end do
430 deallocate(this%stream)
431 end if
432
433 if (allocated(this%event)) then
434 do i = 1, size(this%event)
435 call device_event_destroy(this%event(i))
436 end do
437 deallocate(this%event)
438 end if
439
440 if (c_associated(this%pack_event)) then
441 call device_event_destroy(this%pack_event)
442 this%pack_event = c_null_ptr
443 end if
444#endif
445
446 end subroutine gs_device_shmem_free
447
449 subroutine gs_device_shmem_nbsend(this, u, n, tag, deps, strm)
450 class(gs_device_shmem_t), intent(inout) :: this
451 integer, intent(in) :: n
452 real(kind=rp), dimension(n), intent(inout) :: u
453 integer, intent(in) :: tag
454 type(c_ptr), intent(inout) :: deps
455 type(c_ptr), intent(inout) :: strm
456 integer :: i, parity
457 type(c_ptr) :: u_d
458
459 u_d = device_get_ptr(u)
460
461#ifdef HAVE_NVSHMEM
462 ! One round counter per gs op; advanced here so nbsend and nbwait agree
463 ! on the round's parity slab. The rank-indexed signal slots make the
464 ! exchange independent of peer counts and peer-list ordering.
465 this%iter = this%iter + 1
466 parity = mod(this%iter, 2)
467
468 ! Bulk-pack every peer's slab on the main stream, ordered after the
469 ! gather (deps), BEFORE any per-peer work. This orders all reads of the
470 ! shared buffer u before any unpack write to u. Packing inside the push
471 ! kernel, gated on the peer's ready signal, let an unpack from a fast
472 ! peer modify u before the pack for a slow peer had read it, so a dof
473 ! shared with both peers reached the slow peer already partially
474 ! reduced (divergence at large rank counts, where multi-peer dofs and
475 ! round-level skew are common).
476 !
477 ! The pack needs no remote gate: the parity slab was last put in round
478 ! iter-2, and this pack is stream-ordered (via strm's event joins at
479 ! the end of the previous nbwait) after the unpack of every peer's
480 ! round iter-1 slab. Peer i putting its round iter-1 slab here implies,
481 ! by stream order on peer i -- its push to us and its unpack of our
482 ! data run on one stream, since peer lists are index-aligned (checked
483 ! in init) -- that peer i consumed our round iter-2 slab, which in turn
484 ! implies that put has drained the parity slab.
485 call device_stream_wait_event(strm, deps, 0)
486 call cuda_gs_nvshmem_pack(u_d, this%send_buf%buf_d, &
487 this%send_buf%dof_d, parity * this%send_buf%slab_stride, &
488 this%send_buf%total, strm)
489 call device_event_record(this%pack_event, strm)
490
491 ! Order each per-peer stream after the pack; the push kernels in nbwait
492 ! read the packed parity slab.
493 do i = 1, size(this%send_pe)
494 call device_stream_wait_event(this%stream(i), this%pack_event, 0)
495 end do
496#endif
497
498 ! We do the rest in the "wait" routine below
499
500 end subroutine gs_device_shmem_nbsend
501
503 subroutine gs_device_shmem_nbrecv(this, tag)
504 class(gs_device_shmem_t), intent(inout) :: this
505 integer, intent(in) :: tag
506 integer :: i
507
508 ! We do everything in the "wait" routine below
509
510 end subroutine gs_device_shmem_nbrecv
511
513 subroutine gs_device_shmem_nbwait(this, u, n, op, strm)
514 class(gs_device_shmem_t), intent(inout) :: this
515 integer, intent(in) :: n
516 real(kind=rp), dimension(n), intent(inout) :: u
517 type(c_ptr), intent(inout) :: strm
518 integer :: op, done_req, i, parity
519 type(c_ptr) :: u_d
520
521 u_d = device_get_ptr(u)
522#ifdef HAVE_NVSHMEM
523 parity = mod(this%iter, 2)
524
525 ! Push the packed parity slab to every send peer. The kernel waits for
526 ! the peer's ready signal (previous round consumed, so its recv slab is
527 ! free) before the put; the pack already happened in nbsend.
528 do i = 1, size(this%send_pe)
529 call cuda_gs_push(this%send_buf%buf_d, &
530 parity * this%send_buf%slab_stride + this%send_buf%offset(i), &
531 this%send_buf%ndofs(i), &
532 this%stream(i), &
533 this%send_pe(i), &
534 this%recv_buf%buf_d, &
535 this%send_buf%remote_offset(i), &
536 this%iter, this%done_sig_d, this%ready_sig_d, pe_rank)
537 end do
538
539 ! For every recv peer: wait until its slab has landed, reduce it into u,
540 ! then post our ready signal so the peer may start its next round.
541 do done_req = 1, size(this%recv_pe)
542 call cuda_gs_push_wait(this%stream(done_req), this%iter, &
543 this%done_sig_d, this%recv_pe(done_req))
544 call cuda_gs_unpack(u_d, op, &
545 this%recv_buf%buf_d, &
546 this%recv_buf%dof_d, &
547 this%recv_buf%offset(done_req), &
548 this%recv_buf%ndofs(done_req), &
549 this%stream(done_req))
550 call cuda_gs_post_ready(this%stream(done_req), this%iter, &
551 this%ready_sig_d, pe_rank, this%recv_pe(done_req))
552 call device_event_record(this%event(done_req), this%stream(done_req))
553 end do
554
555 ! Sync non-blocking streams
556 do done_req = 1, size(this%recv_pe)
557 call device_stream_wait_event(strm, &
558 this%event(done_req), 0)
559 end do
560#endif
561 end subroutine gs_device_shmem_nbwait
562
567 subroutine gs_device_shmem_nbsend_vec(this, u, n, nc, tag, deps, strm)
568 class(gs_device_shmem_t), intent(inout) :: this
569 integer, intent(in) :: n, nc
570 real(kind=rp), dimension(nc*n), intent(inout) :: u
571 integer, intent(in) :: tag
572 type(c_ptr), intent(inout) :: deps
573 type(c_ptr), intent(inout) :: strm
574 integer :: i, parity
575 type(c_ptr) :: u_d
576
577 u_d = device_get_ptr(u)
578
579#ifdef HAVE_NVSHMEM
580 ! Shared round counter with the scalar path; all ranks execute the same
581 ! op sequence, so counters stay in lockstep.
582 this%iter = this%iter + 1
583 parity = mod(this%iter, 2)
584
585 call device_stream_wait_event(strm, deps, 0)
586 call cuda_gs_nvshmem_pack_vec(u_d, this%send_buf%buf_v_d, &
587 this%send_buf%dof_d, &
588 parity * gs_vec_nc * this%send_buf%slab_stride, &
589 this%send_buf%total, nc, n, strm)
590 call device_event_record(this%pack_event, strm)
591
592 ! Order each per-peer stream after the pack; the push kernels in
593 ! nbwait_vec read the packed parity slab.
594 do i = 1, size(this%send_pe)
595 call device_stream_wait_event(this%stream(i), this%pack_event, 0)
596 end do
597#endif
598
599 end subroutine gs_device_shmem_nbsend_vec
600
602 subroutine gs_device_shmem_nbrecv_vec(this, tag, nc)
603 class(gs_device_shmem_t), intent(inout) :: this
604 integer, intent(in) :: tag, nc
605 end subroutine gs_device_shmem_nbrecv_vec
606
608 subroutine gs_device_shmem_nbwait_vec(this, u, n, nc, op, strm)
609 class(gs_device_shmem_t), intent(inout) :: this
610 integer, intent(in) :: n, nc
611 real(kind=rp), dimension(nc*n), intent(inout) :: u
612 type(c_ptr), intent(inout) :: strm
613 integer :: op, done_req, i, parity
614 type(c_ptr) :: u_d
615
616 u_d = device_get_ptr(u)
617#ifdef HAVE_NVSHMEM
618 parity = mod(this%iter, 2)
619
620 ! Push the packed nc-component parity slab to every send peer. Offsets
621 ! and counts are in elements (nc values per packed position); the recv
622 ! buffer holds a single slab, so only the local parity offset applies.
623 do i = 1, size(this%send_pe)
624 call cuda_gs_push(this%send_buf%buf_v_d, &
625 parity * gs_vec_nc * this%send_buf%slab_stride &
626 + nc * this%send_buf%offset(i), &
627 nc * this%send_buf%ndofs(i), &
628 this%stream(i), &
629 this%send_pe(i), &
630 this%recv_buf%buf_v_d, &
631 nc * this%send_buf%remote_offset(i), &
632 this%iter, this%done_sig_d, this%ready_sig_d, pe_rank)
633 end do
634
635 ! For every recv peer: wait until its slab has landed, reduce it into u,
636 ! then post our ready signal so the peer may start its next round.
637 do done_req = 1, size(this%recv_pe)
638 call cuda_gs_push_wait(this%stream(done_req), this%iter, &
639 this%done_sig_d, this%recv_pe(done_req))
640 call cuda_gs_unpack_vec(u_d, op, &
641 this%recv_buf%buf_v_d, &
642 this%recv_buf%dof_d, &
643 this%recv_buf%offset(done_req), &
644 this%recv_buf%ndofs(done_req), &
645 nc, n, &
646 this%stream(done_req))
647 call cuda_gs_post_ready(this%stream(done_req), this%iter, &
648 this%ready_sig_d, pe_rank, this%recv_pe(done_req))
649 call device_event_record(this%event(done_req), this%stream(done_req))
650 end do
651
652 ! Sync non-blocking streams
653 do done_req = 1, size(this%recv_pe)
654 call device_stream_wait_event(strm, &
655 this%event(done_req), 0)
656 end do
657#endif
658 end subroutine gs_device_shmem_nbwait_vec
659
660end module gs_device_shmem
Return the device pointer for an associated Fortran array.
Definition device.F90:113
Copy data between host and device (or device and device)
Definition device.F90:72
Definition comm.F90:1
integer, public pe_size
MPI size of communicator.
Definition comm.F90:62
integer, public pe_rank
MPI rank.
Definition comm.F90:59
type(mpi_comm), public neko_comm
MPI communicator.
Definition comm.F90:46
Device abstraction, common interface for various accelerators.
Definition device.F90:34
subroutine, public device_event_record(event, stream)
Record a device event.
Definition device.F90:1644
integer, parameter, public host_to_device
Definition device.F90:48
subroutine, public device_free(x_d)
Deallocate memory on the device.
Definition device.F90:243
subroutine, public device_event_destroy(event)
Destroy a device event.
Definition device.F90:1623
subroutine, public device_alloc(x_d, s)
Allocate memory on the device.
Definition device.F90:212
subroutine, public device_stream_create_with_priority(stream, flags, prio)
Create a device stream/command queue with priority.
Definition device.F90:1500
subroutine, public device_stream_wait_event(stream, event, flags)
Synchronize a device stream with an event.
Definition device.F90:1544
subroutine, public device_event_create(event, flags)
Create a device event queue.
Definition device.F90:1589
integer, public strm_high_prio
High priority stream setting.
Definition device.F90:66
subroutine, public device_stream_destroy(stream)
Destroy a device stream/command queue.
Definition device.F90:1522
Defines a gather-scatter communication method.
Definition gs_comm.f90:34
integer, parameter, public gs_vec_nc
Maximum number of components handled by the fused vector (multi-component) halo exchange used by gs_o...
Definition gs_comm.f90:50
Defines GPU aware MPI gather-scatter communication.
subroutine gs_device_shmem_nbrecv_vec(this, tag, nc)
No-op: everything happens in nbwait_vec.
subroutine gs_device_shmem_buf_init(this, pe_order, dof_stack, mark_dupes, nslabs)
subroutine gs_device_shmem_nbwait(this, u, n, op, strm)
Wait for non-blocking operations.
subroutine gs_device_shmem_nbsend_vec(this, u, n, nc, tag, deps, strm)
Fused nc-component send. u is the compact shared device buffer (component-outer, per-component stride...
subroutine gs_device_shmem_nbsend(this, u, n, tag, deps, strm)
Post non-blocking send operations.
subroutine gs_device_shmem_nbrecv(this, tag)
Post non-blocking receive operations.
subroutine gs_device_shmem_nbwait_vec(this, u, n, nc, op, strm)
Fused nc-component push + unpack (the pack happens in nbsend_vec).
subroutine gs_device_shmem_free(this)
Deallocate MPI based communication method.
subroutine gs_device_shmem_buf_free(this)
subroutine gs_device_shmem_init(this, send_pe, recv_pe)
Initialise MPI based communication method.
Implements a hash table ADT.
Definition htable.f90:52
integer, parameter, public c_rp
Definition num_types.f90:15
integer, parameter, public rp
Global precision used in computations.
Definition num_types.f90:14
Implements a dynamic stack ADT.
Definition stack.f90:49
Utilities.
Definition utils.f90:35
Gather-scatter communication method.
Definition gs_comm.f90:53
Buffers for non-blocking communication and packing/unpacking.
Gather-scatter communication using device SHMEM. The arrays are indexed per PE like send_pe and @ rec...
Integer based hash table.
Definition htable.f90:102
Integer based stack.
Definition stack.f90:77
#define max(a, b)
Definition tensor.cu:40