Neko 1.99.6
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
gs_device_nccl.F90
Go to the documentation of this file.
1! Copyright (c) 2025, 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 comm, only : pe_size, pe_rank
39 use htable, only : htable_i4_t
44 use utils, only : neko_error
45 use, intrinsic :: iso_c_binding, only : c_sizeof, c_int32_t, &
46 c_ptr, c_null_ptr, c_size_t, c_associated, c_int
47 implicit none
48 private
49
51 type, private :: gs_device_nccl_buf_t
52 integer, allocatable :: ndofs(:)
53 integer, allocatable :: offset(:)
54 integer :: total
55 type(c_ptr) :: buf_d = c_null_ptr
56 type(c_ptr) :: buf_v_d = c_null_ptr
57 type(c_ptr) :: dof_d = c_null_ptr
58 contains
59 procedure, pass(this) :: init => gs_device_nccl_buf_init
60 procedure, pass(this) :: free => gs_device_nccl_buf_free
62
65 type, public, extends(gs_comm_t) :: gs_device_nccl_t
66 type(gs_device_nccl_buf_t) :: send_buf
67 type(gs_device_nccl_buf_t) :: recv_buf
68 type(c_ptr), allocatable :: stream(:)
69 type(c_ptr), allocatable :: event(:)
70 integer :: nb_strtgy
71 type(c_ptr) :: send_event = c_null_ptr
72 contains
73 procedure, pass(this) :: init => gs_device_nccl_init
74 procedure, pass(this) :: free => gs_device_nccl_free
75 procedure, pass(this) :: nbsend => gs_device_nccl_nbsend
76 procedure, pass(this) :: nbrecv => gs_device_nccl_nbrecv
77 procedure, pass(this) :: nbwait => gs_device_nccl_nbwait
78 procedure, pass(this) :: nbsend_vec => gs_device_nccl_nbsend_vec
79 procedure, pass(this) :: nbrecv_vec => gs_device_nccl_nbrecv_vec
80 procedure, pass(this) :: nbwait_vec => gs_device_nccl_nbwait_vec
81 end type gs_device_nccl_t
82
83#ifdef HAVE_HIP
84 interface
85 subroutine hip_gs_pack(u_d, buf_d, dof_d, offset, n, stream) &
86 bind(c, name = 'hip_gs_pack')
87 use, intrinsic :: iso_c_binding
88 implicit none
89 integer(c_int), value :: n, offset
90 type(c_ptr), value :: u_d, buf_d, dof_d, stream
91 end subroutine hip_gs_pack
92 end interface
93
94 interface
95 subroutine hip_gs_unpack(u_d, op, buf_d, dof_d, offset, n, stream) &
96 bind(c, name = 'hip_gs_unpack')
97 use, intrinsic :: iso_c_binding
98 implicit none
99 integer(c_int), value :: op, offset, n
100 type(c_ptr), value :: u_d, buf_d, dof_d, stream
101 end subroutine hip_gs_unpack
102 end interface
103
104 interface
105 subroutine hip_gs_pack_vec(u_d, buf_d, dof_d, offset, n, nc, ns, stream) &
106 bind(c, name = 'hip_gs_pack_vec')
107 use, intrinsic :: iso_c_binding
108 implicit none
109 integer(c_int), value :: offset, n, nc, ns
110 type(c_ptr), value :: u_d, buf_d, dof_d, stream
111 end subroutine hip_gs_pack_vec
112 end interface
113
114 interface
115 subroutine hip_gs_unpack_vec(u_d, op, buf_d, dof_d, offset, n, nc, ns, &
116 stream) bind(c, name = 'hip_gs_unpack_vec')
117 use, intrinsic :: iso_c_binding
118 implicit none
119 integer(c_int), value :: op, offset, n, nc, ns
120 type(c_ptr), value :: u_d, buf_d, dof_d, stream
121 end subroutine hip_gs_unpack_vec
122 end interface
123#elif HAVE_CUDA
124 interface
125 subroutine cuda_gs_pack(u_d, buf_d, dof_d, offset, n, stream) &
126 bind(c, name = 'cuda_gs_pack')
127 use, intrinsic :: iso_c_binding
128 implicit none
129 integer(c_int), value :: n, offset
130 type(c_ptr), value :: u_d, buf_d, dof_d, stream
131 end subroutine cuda_gs_pack
132 end interface
133
134 interface
135 subroutine cuda_gs_unpack(u_d, op, buf_d, dof_d, offset, n, stream) &
136 bind(c, name = 'cuda_gs_unpack')
137 use, intrinsic :: iso_c_binding
138 implicit none
139 integer(c_int), value :: op, offset, n
140 type(c_ptr), value :: u_d, buf_d, dof_d, stream
141 end subroutine cuda_gs_unpack
142 end interface
143
144 interface
145 subroutine cuda_gs_pack_vec(u_d, buf_d, dof_d, offset, n, nc, ns, stream) &
146 bind(c, name = 'cuda_gs_pack_vec')
147 use, intrinsic :: iso_c_binding
148 implicit none
149 integer(c_int), value :: offset, n, nc, ns
150 type(c_ptr), value :: u_d, buf_d, dof_d, stream
151 end subroutine cuda_gs_pack_vec
152 end interface
153
154 interface
155 subroutine cuda_gs_unpack_vec(u_d, op, buf_d, dof_d, offset, n, nc, ns, &
156 stream) bind(c, name = 'cuda_gs_unpack_vec')
157 use, intrinsic :: iso_c_binding
158 implicit none
159 integer(c_int), value :: op, offset, n, nc, ns
160 type(c_ptr), value :: u_d, buf_d, dof_d, stream
161 end subroutine cuda_gs_unpack_vec
162 end interface
163#endif
164
165 interface
166 subroutine device_nccl_sendrecv(sbuf_d, soffset, scount, srank, &
167 rbuf_d, roffset, rcount, rrank, nbytes, stream) &
168 bind(c, name = 'device_nccl_sendrecv')
169 use, intrinsic :: iso_c_binding
170 implicit none
171 integer(c_int), value :: soffset, scount, roffset, rcount
172 integer(c_int), value :: srank, rrank, nbytes
173 type(c_ptr), value :: sbuf_d, rbuf_d, stream
174 end subroutine device_nccl_sendrecv
175 end interface
176
177contains
178
179 subroutine gs_device_nccl_buf_init(this, pe_order, dof_stack, mark_dupes)
180 class(gs_device_nccl_buf_t), intent(inout) :: this
181 integer, allocatable, intent(inout) :: pe_order(:)
182 type(stack_i4_t), allocatable, intent(inout) :: dof_stack(:)
183 logical, intent(in) :: mark_dupes
184 integer, allocatable :: dofs(:)
185 integer :: i, j, total
186 integer(c_size_t) :: sz
187 type(htable_i4_t) :: doftable
188 integer :: dupe, marked, k
189 real(c_rp) :: rp_dummy
190 integer(c_int32_t) :: i4_dummy
191
192
193 allocate(this%ndofs(size(pe_order)))
194 allocate(this%offset(size(pe_order)))
195
196 total = 0
197 do i = 1, size(pe_order)
198 this%ndofs(i) = dof_stack(pe_order(i))%size()
199 this%offset(i) = total
200 total = total + this%ndofs(i)
201 end do
202
203 this%total = total
204
205 sz = c_sizeof(rp_dummy) * total
206 call device_alloc(this%buf_d, sz)
207
208 ! Fused vector buffer, sized for up to GS_VEC_NC components.
209 sz = c_sizeof(rp_dummy) * gs_vec_nc * total
210 call device_alloc(this%buf_v_d, sz)
211
212 sz = c_sizeof(i4_dummy) * total
213 call device_alloc(this%dof_d, sz)
214
215 if (mark_dupes) call doftable%init(2*total)
216 allocate(dofs(total))
217
218 ! Copy from dof_stack into dofs, optionally marking duplicates with doftable
219 marked = 0
220 do i = 1, size(pe_order)
221 ! %array() breaks on cray
222 select type (arr => dof_stack(pe_order(i))%data)
223 type is (integer)
224 do j = 1, this%ndofs(i)
225 k = this%offset(i) + j
226 if (mark_dupes) then
227 if (doftable%get(arr(j), dupe) .eq. 0) then
228 if (dofs(dupe) .gt. 0) then
229 dofs(dupe) = -dofs(dupe)
230 marked = marked + 1
231 end if
232 dofs(k) = -arr(j)
233 marked = marked + 1
234 else
235 call doftable%set(arr(j), k)
236 dofs(k) = arr(j)
237 end if
238 else
239 dofs(k) = arr(j)
240 end if
241 end do
242 end select
243 end do
244
245 call device_memcpy(dofs, this%dof_d, total, host_to_device, sync = .true.)
246
247 deallocate(dofs)
248 call doftable%free()
249
250 end subroutine gs_device_nccl_buf_init
251
252 subroutine gs_device_nccl_buf_free(this)
253 class(gs_device_nccl_buf_t), intent(inout) :: this
254
255 if (allocated(this%ndofs)) deallocate(this%ndofs)
256 if (allocated(this%offset)) deallocate(this%offset)
257
258 if (c_associated(this%buf_d)) call device_free(this%buf_d)
259 if (c_associated(this%buf_v_d)) call device_free(this%buf_v_d)
260 if (c_associated(this%dof_d)) call device_free(this%dof_d)
261 end subroutine gs_device_nccl_buf_free
262
264 subroutine gs_device_nccl_init(this, send_pe, recv_pe)
265 class(gs_device_nccl_t), intent(inout) :: this
266 type(stack_i4_t), intent(inout) :: send_pe
267 type(stack_i4_t), intent(inout) :: recv_pe
268 integer :: i, nstrm
269
270#if !defined(HAVE_NCCL) && !defined(HAVE_RCCL)
271 call neko_error('Neko was not built with NCCL support')
272#endif
273
274 call this%init_order(send_pe, recv_pe)
275
276 call this%send_buf%init(this%send_pe, this%send_dof, .false.)
277 call this%recv_buf%init(this%recv_pe, this%recv_dof, .true.)
278
279#if defined(HAVE_HIP) || defined(HAVE_CUDA)
280 ! Create a set of non-blocking streams. The per-peer streams and events
281 ! are indexed over both send_pe (pack, sendrecv) and recv_pe (unpack,
282 ! sync), so size them for the larger of the two peer lists.
283 nstrm = max(size(this%send_pe), size(this%recv_pe))
284 allocate(this%stream(nstrm))
285 do i = 1, nstrm
286 call device_stream_create_with_priority(this%stream(i), 1, &
288 end do
289
290 allocate(this%event(nstrm))
291 do i = 1, nstrm
292 call device_event_create(this%event(i), 2)
293 end do
294#endif
295
296 this%vec_supported = .true.
297
298 end subroutine gs_device_nccl_init
299
301 subroutine gs_device_nccl_free(this)
302 class(gs_device_nccl_t), intent(inout) :: this
303 integer :: i
304
305 call this%send_buf%free()
306 call this%recv_buf%free()
307
308 call this%free_order()
309 call this%free_dofs()
310
311#if defined(HAVE_HIP) || defined(HAVE_CUDA)
312 if (allocated(this%stream)) then
313 do i = 1, size(this%stream)
314 call device_stream_destroy(this%stream(i))
315 end do
316 deallocate(this%stream)
317 end if
318
319 if (allocated(this%event)) then
320 do i = 1, size(this%event)
321 call device_event_destroy(this%event(i))
322 end do
323 deallocate(this%event)
324 end if
325#endif
326
327 end subroutine gs_device_nccl_free
328
330 subroutine gs_device_nccl_nbsend(this, u, n, tag, deps, strm)
331 class(gs_device_nccl_t), intent(inout) :: this
332 integer, intent(in) :: n
333 real(kind=rp), dimension(n), intent(inout) :: u
334 integer, intent(in) :: tag
335 type(c_ptr), intent(inout) :: deps
336 type(c_ptr), intent(inout) :: strm
337 integer :: i
338 type(c_ptr) :: u_d
339
340 u_d = device_get_ptr(u)
341
342 do i = 1, size(this%send_pe)
343 call device_stream_wait_event(this%stream(i), deps, 0)
344#ifdef HAVE_HIP
345 call hip_gs_pack(u_d, &
346 this%send_buf%buf_d, &
347 this%send_buf%dof_d, &
348 this%send_buf%offset(i), &
349 this%send_buf%ndofs(i), &
350 this%stream(i))
351#elif HAVE_CUDA
352 call cuda_gs_pack(u_d, &
353 this%send_buf%buf_d, &
354 this%send_buf%dof_d, &
355 this%send_buf%offset(i), &
356 this%send_buf%ndofs(i), &
357 this%stream(i))
358#else
359 call neko_error('gs_device_nccl: no backend')
360#endif
361 end do
362
363
364 ! Everything else is done in the wait routine
365
366 end subroutine gs_device_nccl_nbsend
367
369 subroutine gs_device_nccl_nbrecv(this, tag)
370 class(gs_device_nccl_t), intent(inout) :: this
371 integer, intent(in) :: tag
372 integer :: i
373
374 ! Everything is done in the wait routine
375
376 end subroutine gs_device_nccl_nbrecv
377
379 subroutine gs_device_nccl_nbwait(this, u, n, op, strm)
380 class(gs_device_nccl_t), intent(inout) :: this
381 integer, intent(in) :: n
382 real(kind=rp), dimension(n), intent(inout) :: u
383 type(c_ptr), intent(inout) :: strm
384 integer :: op, done_req, i
385 type(c_ptr) :: u_d
386 real(c_rp) :: rp_dummy
387 integer(c_int) :: nbytes
388
389 u_d = device_get_ptr(u)
390 nbytes = c_sizeof(rp_dummy)
391
392 do i = 1, size(this%send_pe)
393
394 call device_nccl_sendrecv(this%send_buf%buf_d, &
395 nbytes*this%send_buf%offset(i), &
396 this%send_buf%ndofs(i), &
397 this%send_pe(i), &
398 this%recv_buf%buf_d, &
399 nbytes*this%recv_buf%offset(i), &
400 this%recv_buf%ndofs(i), &
401 this%recv_pe(i), &
402 nbytes, &
403 this%stream(i))
404
405#ifdef HAVE_HIP
406 call hip_gs_unpack(u_d, op, &
407 this%recv_buf%buf_d, &
408 this%recv_buf%dof_d, &
409 this%recv_buf%offset(i), &
410 this%recv_buf%ndofs(i), &
411 this%stream(i))
412#elif HAVE_CUDA
413 call cuda_gs_unpack(u_d, op, &
414 this%recv_buf%buf_d, &
415 this%recv_buf%dof_d, &
416 this%recv_buf%offset(i), &
417 this%recv_buf%ndofs(i), &
418 this%stream(i))
419#else
420 call neko_error('gs_device_mpi: no backend')
421#endif
422 call device_event_record(this%event(i), this%stream(i))
423 end do
424
425 ! Sync non-blocking streams
426 do done_req = 1, size(this%recv_pe)
427 call device_stream_wait_event(strm, &
428 this%event(done_req), 0)
429 end do
430
431 end subroutine gs_device_nccl_nbwait
432
436 subroutine gs_device_nccl_nbsend_vec(this, u, n, nc, tag, deps, strm)
437 class(gs_device_nccl_t), intent(inout) :: this
438 integer, intent(in) :: n, nc
439 real(kind=rp), dimension(nc*n), intent(inout) :: u
440 integer, intent(in) :: tag
441 type(c_ptr), intent(inout) :: deps
442 type(c_ptr), intent(inout) :: strm
443 integer :: i
444 type(c_ptr) :: u_d
445
446 u_d = device_get_ptr(u)
447
448 do i = 1, size(this%send_pe)
449 call device_stream_wait_event(this%stream(i), deps, 0)
450#ifdef HAVE_HIP
451 call hip_gs_pack_vec(u_d, this%send_buf%buf_v_d, this%send_buf%dof_d, &
452 this%send_buf%offset(i), this%send_buf%ndofs(i), nc, n, &
453 this%stream(i))
454#elif HAVE_CUDA
455 call cuda_gs_pack_vec(u_d, this%send_buf%buf_v_d, this%send_buf%dof_d, &
456 this%send_buf%offset(i), this%send_buf%ndofs(i), nc, n, &
457 this%stream(i))
458#else
459 call neko_error('gs_device_nccl: no backend')
460#endif
461 end do
462
463 end subroutine gs_device_nccl_nbsend_vec
464
466 subroutine gs_device_nccl_nbrecv_vec(this, tag, nc)
467 class(gs_device_nccl_t), intent(inout) :: this
468 integer, intent(in) :: tag, nc
469 end subroutine gs_device_nccl_nbrecv_vec
470
472 subroutine gs_device_nccl_nbwait_vec(this, u, n, nc, op, strm)
473 class(gs_device_nccl_t), intent(inout) :: this
474 integer, intent(in) :: n, nc
475 real(kind=rp), dimension(nc*n), intent(inout) :: u
476 type(c_ptr), intent(inout) :: strm
477 integer :: op, done_req, i
478 type(c_ptr) :: u_d
479 real(c_rp) :: rp_dummy
480 integer(c_int) :: nbytes
481
482 u_d = device_get_ptr(u)
483 nbytes = c_sizeof(rp_dummy)
484
485 do i = 1, size(this%send_pe)
486
487 call device_nccl_sendrecv(this%send_buf%buf_v_d, &
488 nbytes*nc*this%send_buf%offset(i), &
489 nc*this%send_buf%ndofs(i), &
490 this%send_pe(i), &
491 this%recv_buf%buf_v_d, &
492 nbytes*nc*this%recv_buf%offset(i), &
493 nc*this%recv_buf%ndofs(i), &
494 this%recv_pe(i), &
495 nbytes, &
496 this%stream(i))
497
498#ifdef HAVE_HIP
499 call hip_gs_unpack_vec(u_d, op, this%recv_buf%buf_v_d, &
500 this%recv_buf%dof_d, this%recv_buf%offset(i), &
501 this%recv_buf%ndofs(i), nc, n, this%stream(i))
502#elif HAVE_CUDA
503 call cuda_gs_unpack_vec(u_d, op, this%recv_buf%buf_v_d, &
504 this%recv_buf%dof_d, this%recv_buf%offset(i), &
505 this%recv_buf%ndofs(i), nc, n, this%stream(i))
506#else
507 call neko_error('gs_device_nccl: no backend')
508#endif
509 call device_event_record(this%event(i), this%stream(i))
510 end do
511
512 ! Sync non-blocking streams
513 do done_req = 1, size(this%recv_pe)
514 call device_stream_wait_event(strm, &
515 this%event(done_req), 0)
516 end do
517
518 end subroutine gs_device_nccl_nbwait_vec
519
520end module gs_device_nccl
void cuda_gs_unpack(real *u_d, int op, real *buf_d, int *dof_d, int offset, int n, cudaStream_t stream)
Definition gs.cu:132
void cuda_gs_pack(void *u_d, void *buf_d, void *dof_d, int offset, int n, cudaStream_t stream)
Definition gs.cu:116
void cuda_gs_pack_vec(void *u_d, void *buf_d, void *dof_d, int offset, int n, int nc, int ns, cudaStream_t stream)
Definition gs.cu:166
void cuda_gs_unpack_vec(real *u_d, int op, real *buf_d, int *dof_d, int offset, int n, int nc, int ns, cudaStream_t stream)
Definition gs.cu:183
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
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:49
Defines NCCL based gather-scatter communication.
subroutine gs_device_nccl_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_nccl_nbrecv(this, tag)
Post non-blocking receive operations.
subroutine gs_device_nccl_init(this, send_pe, recv_pe)
Initialise NCCL based communication method.
subroutine gs_device_nccl_nbsend(this, u, n, tag, deps, strm)
Post non-blocking send operations.
subroutine gs_device_nccl_nbwait(this, u, n, op, strm)
Wait for non-blocking operations.
subroutine gs_device_nccl_buf_init(this, pe_order, dof_stack, mark_dupes)
subroutine gs_device_nccl_nbrecv_vec(this, tag, nc)
No-op: send/recv and unpack happen in nbwait_vec.
subroutine gs_device_nccl_nbwait_vec(this, u, n, nc, op, strm)
Fused nc-component send/recv + unpack.
subroutine gs_device_nccl_free(this)
Deallocate MPI based communication method.
subroutine gs_device_nccl_buf_free(this)
Implements a hash table ADT.
Definition htable.f90:52
integer, parameter, public c_rp
Definition num_types.f90:13
integer, parameter, public rp
Global precision used in computations.
Definition num_types.f90:12
Implements a dynamic stack ADT.
Definition stack.f90:49
Utilities.
Definition utils.f90:35
Gather-scatter communication method.
Definition gs_comm.f90:52
Buffers for non-blocking communication and packing/unpacking.
Gather-scatter communication using NCCL The arrays are indexed per PE like send_pe and @ recv_pe.
Integer based hash table.
Definition htable.f90:102
Integer based stack.
Definition stack.f90:77
#define max(a, b)
Definition tensor.cu:40