Neko 1.99.9
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
gs_utofu.F90
Go to the documentation of this file.
1! Copyright (c) 2026, 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!
44 use num_types, only : rp, i8
47 use stack, only : stack_i4_t
48 use comm, only : neko_comm, pe_rank
49 use logger, only : neko_log, log_size
50#ifdef HAVE_UTOFU
51 use mpi_f08, only : mpi_request, mpi_isend, mpi_irecv, mpi_waitall, &
52 mpi_integer8, mpi_statuses_ignore
53#endif
54 use utils, only : neko_error
55 !$ use omp_lib, only : omp_get_max_threads, omp_get_thread_num, &
56 !$ omp_get_num_threads
57 use, intrinsic :: iso_c_binding
58 implicit none
59 private
60
64#ifdef HAVE_UTOFU
65 logical, parameter, public :: gs_utofu_avail = .true.
66#else
67 logical, parameter, public :: gs_utofu_avail = .false.
68#endif
69
71 integer, parameter :: gs_utofu_xchg_tag = 8123
73 integer, parameter :: gs_utofu_xchg_tag_v = 8124
74
76 type, public, extends(gs_comm_t) :: gs_utofu_t
80 real(kind=rp), pointer :: send_buf(:) => null()
84 real(kind=rp), pointer :: recv_buf(:) => null()
86 integer, allocatable :: send_len(:), recv_len(:)
87 integer, allocatable :: send_offset(:), recv_offset(:)
89 integer(c_int64_t), allocatable :: send_off64(:), send_len64(:)
93 integer(c_int64_t), allocatable :: rmt_vcq_id(:), rmt_stadd(:)
94 integer(c_int64_t), allocatable :: dst_off0(:), dst_off1(:)
95 integer(c_int), allocatable :: dst_tag(:)
98 integer(c_int), allocatable :: recv_indices(:)
99 integer(c_int) :: ncompleted = 0
101 integer :: buf_size = 0
105 integer :: send_size = 0
107 integer :: elem_size = 0
109 integer :: parity = 0
111 type(c_ptr) :: ctx = c_null_ptr
119 real(kind=rp), pointer :: send_buf_v(:) => null()
120 real(kind=rp), pointer :: recv_buf_v(:) => null()
125 integer(c_int64_t), allocatable :: rmt_vcq_id_v(:), rmt_stadd_v(:)
126 integer(c_int64_t), allocatable :: rmt_off_v(:), rmt_bufsz_v(:)
130 integer(c_int64_t), allocatable :: send_off64_v(:), send_len64_v(:)
131 integer(c_int64_t), allocatable :: dst_off_v(:)
133 integer :: parity_v = 0
135 type(c_ptr) :: ctx_v = c_null_ptr
136 contains
137 procedure, pass(this) :: init => gs_utofu_init
138 procedure, pass(this) :: free => gs_utofu_free
139 procedure, pass(this) :: nbsend => gs_nbsend_utofu
140 procedure, pass(this) :: nbrecv => gs_nbrecv_utofu
141 procedure, pass(this) :: nbwait => gs_nbwait_utofu
142 procedure, pass(this) :: nbsend_vec => gs_nbsend_vec_utofu
143 procedure, pass(this) :: nbrecv_vec => gs_nbrecv_vec_utofu
144 procedure, pass(this) :: nbwait_vec => gs_nbwait_vec_utofu
145 end type gs_utofu_t
146
147#ifdef HAVE_UTOFU
148
149 integer(c_int64_t) :: gs_utofu_edata_max = 255
152 integer(c_int) :: gs_utofu_nrvcq = 1
154 logical, save :: gs_utofu_logged = .false.
155
156 interface
157 subroutine gs_utofu_init_ep(ntni_req, nthreads, ntni_out, nvcq_out, &
158 nrvcq_out, edata_max, ierr) bind(c, name = 'gs_utofu_init')
159 import :: c_int, c_int64_t
160 integer(c_int), value :: ntni_req, nthreads
161 integer(c_int) :: ntni_out, nvcq_out, nrvcq_out
162 integer(c_int64_t) :: edata_max
163 integer(c_int) :: ierr
164 end subroutine gs_utofu_init_ep
165
166 subroutine gs_utofu_ctx_create(send_buf, send_bytes, recv_buf, &
167 recv_bytes, ctx, nrvcq, recv_vcq_id, recv_stadd, ierr) &
168 bind(c, name = 'gs_utofu_ctx_create')
169 import :: c_ptr, c_size_t, c_int64_t, c_int
170 type(c_ptr), value :: send_buf, recv_buf
171 integer(c_size_t), value :: send_bytes, recv_bytes
172 type(c_ptr) :: ctx
173 integer(c_int) :: nrvcq
174 integer(c_int64_t) :: recv_vcq_id(*)
175 integer(c_int64_t) :: recv_stadd(*)
176 integer(c_int) :: ierr
177 end subroutine gs_utofu_ctx_create
178
179 subroutine gs_utofu_ctx_free(ctx, ierr) bind(c, name = 'gs_utofu_ctx_free')
180 import :: c_ptr, c_int
181 type(c_ptr), value :: ctx
182 integer(c_int) :: ierr
183 end subroutine gs_utofu_ctx_free
184
185 subroutine gs_utofu_post_puts(ctx, tid, nteam, nsend, send_off, &
186 send_len, rmt_vcq_id, rmt_stadd, dst_off0, dst_off1, dst_tag, &
187 parity, send_base, elem_size, ierr) &
188 bind(c, name = 'gs_utofu_post_puts')
189 import :: c_ptr, c_int, c_int64_t
190 type(c_ptr), value :: ctx
191 integer(c_int), value :: tid, nteam, nsend, parity, elem_size
192 integer(c_int64_t) :: send_off(*), send_len(*)
193 integer(c_int64_t) :: rmt_vcq_id(*), rmt_stadd(*)
194 integer(c_int64_t) :: dst_off0(*), dst_off1(*)
195 integer(c_int) :: dst_tag(*)
196 integer(c_int64_t), value :: send_base
197 integer(c_int) :: ierr
198 end subroutine gs_utofu_post_puts
199
200 subroutine gs_utofu_poll_recv(ctx, parity, cap, ncompleted, out_idx, &
201 ierr) bind(c, name = 'gs_utofu_poll_recv')
202 import :: c_ptr, c_int
203 type(c_ptr), value :: ctx
204 integer(c_int), value :: parity, cap
205 integer(c_int) :: ncompleted
206 integer(c_int) :: out_idx(*)
207 integer(c_int) :: ierr
208 end subroutine gs_utofu_poll_recv
209
210 subroutine gs_utofu_drain_half(ctx, half, tid, nteam, ierr) &
211 bind(c, name = 'gs_utofu_drain_half')
212 import :: c_ptr, c_int
213 type(c_ptr), value :: ctx
214 integer(c_int), value :: half, tid, nteam
215 integer(c_int) :: ierr
216 end subroutine gs_utofu_drain_half
217 end interface
218#endif
219
220contains
221
223 subroutine gs_utofu_init(this, send_pe, recv_pe)
224 class(gs_utofu_t), intent(inout) :: this
225 type(stack_i4_t), intent(inout) :: send_pe
226 type(stack_i4_t), intent(inout) :: recv_pe
227#ifdef HAVE_UTOFU
228 integer :: i, nsend, nrecv, send_total, recv_total
229 integer :: ntni_req, env_len, max_tag, nthreads
230 integer(c_int) :: ntni, nvcq, nrv, ierr
231 integer(c_int64_t), allocatable :: recv_vcq_id(:), recv_stadd(:)
232 integer(c_size_t) :: send_bytes, recv_bytes
233 character(len=32) :: env_val
234 character(len=LOG_SIZE) :: log_buf
235 integer(c_int64_t), allocatable :: msg_out(:,:), msg_in(:,:)
236 type(mpi_request), allocatable :: sreq(:), rreq(:)
237
238 call this%init_order(send_pe, recv_pe)
239
240 nsend = size(this%send_pe)
241 nrecv = size(this%recv_pe)
242 this%elem_size = int(storage_size(1.0_rp) / 8)
243
244 allocate(this%send_len(nsend), this%send_offset(nsend))
245 allocate(this%send_off64(nsend), this%send_len64(nsend))
246 allocate(this%rmt_vcq_id(nsend), this%rmt_stadd(nsend))
247 allocate(this%dst_off0(nsend), this%dst_off1(nsend), this%dst_tag(nsend))
248 allocate(this%recv_len(nrecv), this%recv_offset(nrecv))
249 allocate(this%recv_indices(nrecv))
250
251 ! Local receive layout (one concatenated half; the other half mirrors it).
252 recv_total = 0
253 do i = 1, nrecv
254 this%recv_len(i) = this%recv_dof(this%recv_pe(i))%size()
255 this%recv_offset(i) = recv_total
256 recv_total = recv_total + this%recv_len(i)
257 end do
258 this%buf_size = recv_total
259
260 ! Local send layout (concatenated per-peer slabs).
261 send_total = 0
262 do i = 1, nsend
263 this%send_len(i) = this%send_dof(this%send_pe(i))%size()
264 this%send_offset(i) = send_total
265 this%send_off64(i) = int(send_total, c_int64_t)
266 this%send_len64(i) = int(this%send_len(i), c_int64_t)
267 send_total = send_total + this%send_len(i)
268 end do
269 this%send_size = send_total
270
271 allocate(this%send_buf(max(1, 2 * send_total)))
272 allocate(this%recv_buf(max(1, 2 * recv_total)))
273
274 ! Create the (process-global) uTofu endpoint and register our buffers.
275 ntni_req = 1
276 call get_environment_variable("NEKO_GS_UTOFU_NTNI", env_val, env_len)
277 if (env_len .gt. 0) read(env_val(1:env_len), *) ntni_req
278
279 ! One injection VCQ is requested per OpenMP thread, dealt over the TNIs.
280 nthreads = 1
281 !$ nthreads = omp_get_max_threads()
282
283 call gs_utofu_init_ep(int(ntni_req, c_int), int(nthreads, c_int), &
284 ntni, nvcq, gs_utofu_nrvcq, gs_utofu_edata_max, ierr)
285 if (ierr .ne. 0) call neko_error("gs_utofu: endpoint init failed")
286
287 send_bytes = int(size(this%send_buf), c_size_t) * this%elem_size
288 recv_bytes = int(size(this%recv_buf), c_size_t) * this%elem_size
289 ! Filled by gs_utofu_ctx_create: one (vcq_id, stadd) pair per receive
290 ! VCQ granted to this instance; each receive peer is bound to one pair
291 ! below. Initialised to keep the compiler from warning (it cannot see
292 ! the C side write).
293 allocate(recv_vcq_id(gs_utofu_nrvcq), recv_stadd(gs_utofu_nrvcq))
294 recv_vcq_id = 0_c_int64_t
295 recv_stadd = 0_c_int64_t
296 nrv = 1
297 call gs_utofu_ctx_create(c_loc(this%send_buf), send_bytes, &
298 c_loc(this%recv_buf), recv_bytes, this%ctx, nrv, recv_vcq_id, &
299 recv_stadd, ierr)
300 if (ierr .eq. 1) then
301 call neko_error("gs_utofu: out of VCQs for the receive queues " // &
302 "(lower OMP_NUM_THREADS or NEKO_GS_UTOFU_NRVCQ, or set " // &
303 "NEKO_GS_UTOFU_NVCQ)")
304 else if (ierr .ne. 0) then
305 call neko_error("gs_utofu: buffer registration failed")
306 end if
307
308 ! Report what the hardware actually granted: requested vs granted can
309 ! differ when ranks on a node share its TNIs, or when the VCQ budget
310 ! runs dry. Once, on rank 0.
311 if (.not. gs_utofu_logged .and. pe_rank .eq. 0) then
312 write(log_buf, '(A,I0,A,I0,A,I0,A,I0,A)') 'uTofu inj. : ', &
313 int(nvcq), ' VCQs (', nthreads, ' threads) over ', int(ntni), &
314 ' TNIs (', ntni_req, ' requested)'
315 call neko_log%message(log_buf)
316 write(log_buf, '(A,I0,A,I0,A)') 'uTofu recv : ', int(nrv), &
317 ' of ', int(gs_utofu_nrvcq), ' VCQs (first instance)'
318 call neko_log%message(log_buf)
319 gs_utofu_logged = .true.
320 end if
321
322 ! Tell each sender where, and with which tag, to put our slab; learn the
323 ! same for each receiver we send to. Message layout per peer:
324 ! [recv_stadd, recv_vcq_id, byte_off_parity0, byte_off_parity1, tag].
325 allocate(msg_out(5, max(1, nrecv)), msg_in(5, max(1, nsend)))
326 allocate(sreq(max(1, nrecv)), rreq(max(1, nsend)))
327
328 ! Receive peer i is bound to receive VCQ mod(i-1, nrv) + 1; advertising
329 ! that VCQ's STADD/id spreads the incoming slabs over the TNIs.
330 do i = 1, nrecv
331 msg_out(1, i) = recv_stadd(mod(i - 1, int(nrv)) + 1)
332 msg_out(2, i) = recv_vcq_id(mod(i - 1, int(nrv)) + 1)
333 msg_out(3, i) = int(this%recv_offset(i), c_int64_t)
334 msg_out(4, i) = int(this%buf_size + this%recv_offset(i), c_int64_t)
335 msg_out(5, i) = int(i - 1, c_int64_t)
336 end do
337
338 do i = 1, nsend
339 call mpi_irecv(msg_in(:, i), 5, mpi_integer8, this%send_pe(i), &
341 end do
342 do i = 1, nrecv
343 call mpi_isend(msg_out(:, i), 5, mpi_integer8, this%recv_pe(i), &
345 end do
346 if (nsend .gt. 0) call mpi_waitall(nsend, rreq, mpi_statuses_ignore)
347 if (nrecv .gt. 0) call mpi_waitall(nrecv, sreq, mpi_statuses_ignore)
348
349 max_tag = 0
350 do i = 1, nsend
351 this%rmt_stadd(i) = msg_in(1, i)
352 this%rmt_vcq_id(i) = msg_in(2, i)
353 this%dst_off0(i) = msg_in(3, i)
354 this%dst_off1(i) = msg_in(4, i)
355 this%dst_tag(i) = int(msg_in(5, i), c_int)
356 max_tag = max(max_tag, this%dst_tag(i))
357 end do
358
359 deallocate(msg_out, msg_in, sreq, rreq)
360
361 ! The edata carries the slab tag in its high bits and the parity bit in
362 ! bit 0, so a neighbour's count must fit (2*tag + 1) in the edata range.
363 ! The vector path reuses the same tags, so this check covers it too.
364 if (int(2 * max_tag + 1, i8) .gt. int(gs_utofu_edata_max, i8)) &
365 call neko_error("gs_utofu: neighbour count exceeds edata capacity")
366
367 ! Fused vector path: double-buffered slabs sized for GS_VEC_NC
368 ! components, registered under their own context (own receive VCQs).
369 allocate(this%send_buf_v(max(1, 2 * gs_vec_nc * send_total)))
370 allocate(this%recv_buf_v(max(1, 2 * gs_vec_nc * recv_total)))
371 allocate(this%rmt_vcq_id_v(nsend), this%rmt_stadd_v(nsend))
372 allocate(this%rmt_off_v(nsend), this%rmt_bufsz_v(nsend))
373 allocate(this%send_off64_v(nsend), this%send_len64_v(nsend))
374 allocate(this%dst_off_v(nsend))
375
376 send_bytes = int(size(this%send_buf_v), c_size_t) * this%elem_size
377 recv_bytes = int(size(this%recv_buf_v), c_size_t) * this%elem_size
378 recv_vcq_id = 0_c_int64_t
379 recv_stadd = 0_c_int64_t
380 nrv = 1
381 call gs_utofu_ctx_create(c_loc(this%send_buf_v), send_bytes, &
382 c_loc(this%recv_buf_v), recv_bytes, this%ctx_v, nrv, recv_vcq_id, &
383 recv_stadd, ierr)
384 if (ierr .eq. 1) then
385 call neko_error("gs_utofu: out of VCQs for the vector receive " // &
386 "queues (lower OMP_NUM_THREADS or NEKO_GS_UTOFU_NRVCQ, or " // &
387 "set NEKO_GS_UTOFU_NVCQ)")
388 else if (ierr .ne. 0) then
389 call neko_error("gs_utofu: vector buffer registration failed")
390 end if
391
392 ! Second metadata exchange for the vector path. Because the component
393 ! count nc varies per round, the receiver advertises its UNSCALED layout
394 ! -- base STADD, VCQ id, our slab's scalar offset, and its scalar half
395 ! size -- and the sender scales by nc and folds the parity in at round
396 ! time. Tags are the scalar ones (same peer ordering).
397 allocate(msg_out(4, max(1, nrecv)), msg_in(4, max(1, nsend)))
398 allocate(sreq(max(1, nrecv)), rreq(max(1, nsend)))
399
400 ! Same peer-to-receive-VCQ binding as the scalar path (its own VCQ set).
401 do i = 1, nrecv
402 msg_out(1, i) = recv_stadd(mod(i - 1, int(nrv)) + 1)
403 msg_out(2, i) = recv_vcq_id(mod(i - 1, int(nrv)) + 1)
404 msg_out(3, i) = int(this%recv_offset(i), c_int64_t)
405 msg_out(4, i) = int(this%buf_size, c_int64_t)
406 end do
407
408 do i = 1, nsend
409 call mpi_irecv(msg_in(:, i), 4, mpi_integer8, this%send_pe(i), &
411 end do
412 do i = 1, nrecv
413 call mpi_isend(msg_out(:, i), 4, mpi_integer8, this%recv_pe(i), &
415 end do
416 if (nsend .gt. 0) call mpi_waitall(nsend, rreq, mpi_statuses_ignore)
417 if (nrecv .gt. 0) call mpi_waitall(nrecv, sreq, mpi_statuses_ignore)
418
419 do i = 1, nsend
420 this%rmt_stadd_v(i) = msg_in(1, i)
421 this%rmt_vcq_id_v(i) = msg_in(2, i)
422 this%rmt_off_v(i) = msg_in(3, i)
423 this%rmt_bufsz_v(i) = msg_in(4, i)
424 end do
425
426 deallocate(msg_out, msg_in, sreq, rreq)
427
428 ! Runtime kill-switch for the fused vector path (validation aid):
429 ! NEKO_GS_UTOFU_VEC=0 makes gs_op_r3 fall back to three scalar rounds.
430 ! The vector context stays registered either way; only its use is gated.
431 call get_environment_variable("NEKO_GS_UTOFU_VEC", env_val, env_len)
432 this%vec_supported = .not. (env_len .gt. 0 .and. env_val(1:1) .eq. '0')
433 ! The vector slabs are part of the symmetric/registered allocation
434 ! made above, which every rank has to take part in, so they cannot
435 ! be deferred to the first fused exchange: a rank with no shared
436 ! dofs never reaches it. See gs_comm_t%vec_ready.
437 this%vec_ready = .true.
438#else
439 call neko_error("uTofu support not built; reconfigure with --with-utofu")
440#endif
441 end subroutine gs_utofu_init
442
444 subroutine gs_utofu_free(this)
445 class(gs_utofu_t), intent(inout) :: this
446#ifdef HAVE_UTOFU
447 integer(c_int) :: ierr
448
449 if (c_associated(this%ctx)) then
450 call gs_utofu_ctx_free(this%ctx, ierr)
451 this%ctx = c_null_ptr
452 end if
453
454 if (c_associated(this%ctx_v)) then
455 call gs_utofu_ctx_free(this%ctx_v, ierr)
456 this%ctx_v = c_null_ptr
457 end if
458
459 if (associated(this%send_buf)) then
460 deallocate(this%send_buf)
461 end if
462
463 if (associated(this%recv_buf)) then
464 deallocate(this%recv_buf)
465 end if
466
467 if (associated(this%send_buf_v)) then
468 deallocate(this%send_buf_v)
469 end if
470
471 if (associated(this%recv_buf_v)) then
472 deallocate(this%recv_buf_v)
473 end if
474
475 if (allocated(this%send_len)) then
476 deallocate(this%send_len)
477 end if
478
479 if (allocated(this%recv_len)) then
480 deallocate(this%recv_len)
481 end if
482
483 if (allocated(this%send_offset)) then
484 deallocate(this%send_offset)
485 end if
486
487 if (allocated(this%recv_offset)) then
488 deallocate(this%recv_offset)
489 end if
490
491 if (allocated(this%send_off64)) then
492 deallocate(this%send_off64)
493 end if
494
495 if (allocated(this%send_len64)) then
496 deallocate(this%send_len64)
497 end if
498
499 if (allocated(this%rmt_vcq_id)) then
500 deallocate(this%rmt_vcq_id)
501 end if
502
503 if (allocated(this%rmt_stadd)) then
504 deallocate(this%rmt_stadd)
505 end if
506
507 if (allocated(this%dst_off0)) then
508 deallocate(this%dst_off0)
509 end if
510
511 if (allocated(this%dst_off1)) then
512 deallocate(this%dst_off1)
513 end if
514
515 if (allocated(this%dst_tag)) then
516 deallocate(this%dst_tag)
517 end if
518
519 if (allocated(this%recv_indices)) then
520 deallocate(this%recv_indices)
521 end if
522
523 if (allocated(this%rmt_vcq_id_v)) then
524 deallocate(this%rmt_vcq_id_v)
525 end if
526
527 if (allocated(this%rmt_stadd_v)) then
528 deallocate(this%rmt_stadd_v)
529 end if
530
531 if (allocated(this%rmt_off_v)) then
532 deallocate(this%rmt_off_v)
533 end if
534
535 if (allocated(this%rmt_bufsz_v)) then
536 deallocate(this%rmt_bufsz_v)
537 end if
538
539 if (allocated(this%send_off64_v)) then
540 deallocate(this%send_off64_v)
541 end if
542
543 if (allocated(this%send_len64_v)) then
544 deallocate(this%send_len64_v)
545 end if
546
547 if (allocated(this%dst_off_v)) then
548 deallocate(this%dst_off_v)
549 end if
550
551 call this%free_order()
552 call this%free_dofs()
553#endif
554 end subroutine gs_utofu_free
555
557 subroutine gs_nbsend_utofu(this, u, n, tag, deps, strm)
558 class(gs_utofu_t), intent(inout) :: this
559 integer, intent(in) :: n
560 real(kind=rp), dimension(n), intent(inout) :: u
561 integer, intent(in) :: tag
562 type(c_ptr), intent(inout) :: deps
563 type(c_ptr), intent(inout) :: strm
564#ifdef HAVE_UTOFU
565 integer :: i, j, dst, off, ndst, send_base, tid, nteam
566 integer(c_int) :: ierr
567 integer, pointer :: sp(:)
568
569 ! Entered from inside the gs_op_vector OpenMP parallel region, and every
570 ! phase is work-shared: each thread owns one injection VCQ (created
571 ! THREAD_SAFE) and fires the puts for its share of the peers itself --
572 ! the uTofu substitute for the per-thread MPI_Isend that Fujitsu MPI's
573 ! missing MPI_THREAD_MULTIPLE support rules out.
574 tid = 0
575 nteam = 1
576 !$ tid = omp_get_thread_num()
577 !$ nteam = omp_get_num_threads()
578
579 ! Reclaim the send-buffer half we are about to repack: the team strides
580 ! over the injection VCQs so each one is drained by exactly one thread
581 ! (no atomics on the completion counters). The half was last used two
582 ! rounds ago, so its puts have long completed -- this just reaps their
583 ! TCQ notices. The barrier then guarantees the whole half is free before
584 ! anyone packs into it.
585 call gs_utofu_drain_half(this%ctx, int(this%parity, c_int), &
586 int(tid, c_int), int(nteam, c_int), ierr)
587 if (ierr .ne. 0) call neko_error("gs_utofu: send drain failed")
588 !$omp barrier
589
590 ! Pack each peer's slab in one work-shared loop. Subroutine locals are
591 ! per-thread private (orphaned region); the implicit barrier at end do
592 ! guarantees every slab is packed before any thread fires its puts (a
593 ! peer packed by one thread may be sent by another -- the pack and post
594 ! partitions differ).
595 send_base = this%parity * this%send_size
596 !$omp do
597 do i = 1, size(this%send_pe)
598 dst = this%send_pe(i)
599 off = this%send_offset(i)
600 ndst = this%send_len(i)
601 sp => this%send_dof(dst)%array()
602 !OCL NORECURRENCE, NOVREC, NOALIAS
603 !DIR$ CONCURRENT
604 !DIR$ IVDEP
605 !GCC$ ivdep
606 !NEC$ IVDEP
607 !$omp simd
608 do j = 1, ndst
609 this%send_buf(send_base + off + j) = u(sp(j))
610 end do
611 end do
612 !$omp end do
613
614 ! Fire the one-sided puts: each thread injects its modulo share of the
615 ! peers on its own VCQ (or thread 0 injects everything when
616 ! NEKO_GS_UTOFU_MASTER_INJECT is set, as an A/B reference). The trailing
617 ! barrier holds the team until every put has been issued.
618 call gs_utofu_post_puts(this%ctx, int(tid, c_int), int(nteam, c_int), &
619 int(size(this%send_pe), c_int), &
620 this%send_off64, this%send_len64, this%rmt_vcq_id, this%rmt_stadd, &
621 this%dst_off0, this%dst_off1, this%dst_tag, &
622 int(this%parity, c_int), int(send_base, c_int64_t), &
623 int(this%elem_size, c_int), ierr)
624 if (ierr .ne. 0) call neko_error("gs_utofu: put failed")
625 !$omp barrier
626#else
627 call neko_error("uTofu support not built")
628#endif
629 end subroutine gs_nbsend_utofu
630
633 subroutine gs_nbrecv_utofu(this, tag)
634 class(gs_utofu_t), intent(inout) :: this
635 integer, intent(in) :: tag
636 end subroutine gs_nbrecv_utofu
637
640 subroutine gs_nbwait_utofu(this, u, n, op, strm)
641 class(gs_utofu_t), intent(inout) :: this
642 integer, intent(in) :: n
643 real(kind=rp), dimension(n), intent(inout) :: u
644 type(c_ptr), intent(inout) :: strm
645 integer :: op
646#ifdef HAVE_UTOFU
647 integer :: i, j, k, src, off, nsrc, nreqs, base
648 integer(c_int) :: ierr
649 integer, pointer :: sp(:)
650 ! Bind a local pointer to the receive buffer and index it in the unpack
651 ! loops, mirroring how the CAF backend uses its module-level buffer.
652 real(kind=rp), pointer :: rbuf(:)
653
654 base = this%parity * this%buf_size
655 nreqs = size(this%recv_pe)
656 rbuf => this%recv_buf
657
658 do while (nreqs .gt. 0)
659 !$omp master
660 ! Spin on the MRQ from the master alone until at least one slab lands.
661 ! Polling here (rather than around the whole team) keeps the idle
662 ! threads from paying a barrier on every empty poll -- the dominant
663 ! cost under interconnect latency.
664 this%ncompleted = 0
665 do while (this%ncompleted .eq. 0)
666 call gs_utofu_poll_recv(this%ctx, int(this%parity, c_int), &
667 int(size(this%recv_pe), c_int), this%ncompleted, &
668 this%recv_indices, ierr)
669 if (ierr .ne. 0) call neko_error("gs_utofu: mrq poll failed")
670 end do
671 !$omp end master
672 !$omp barrier
673
674 do k = 1, this%ncompleted
675 i = this%recv_indices(k)
676 src = this%recv_pe(i)
677 off = this%recv_offset(i)
678 nsrc = this%recv_len(i)
679 sp => this%recv_dof(src)%array()
680 select case (op)
681 case (gs_op_add)
682 !OCL NORECURRENCE, NOVREC, NOALIAS
683 !DIR$ CONCURRENT
684 !DIR$ IVDEP
685 !GCC$ ivdep
686 !NEC$ IVDEP
687 !$omp do
688 do j = 1, nsrc
689 u(sp(j)) = u(sp(j)) + rbuf(base + off + j)
690 end do
691 !$omp end do
692 case (gs_op_mul)
693 !OCL NORECURRENCE, NOVREC, NOALIAS
694 !DIR$ CONCURRENT
695 !DIR$ IVDEP
696 !GCC$ ivdep
697 !NEC$ IVDEP
698 !$omp do
699 do j = 1, nsrc
700 u(sp(j)) = u(sp(j)) * rbuf(base + off + j)
701 end do
702 !$omp end do
703 case (gs_op_min)
704 !OCL NORECURRENCE, NOVREC, NOALIAS
705 !DIR$ CONCURRENT
706 !DIR$ IVDEP
707 !GCC$ ivdep
708 !NEC$ IVDEP
709 !$omp do
710 do j = 1, nsrc
711 u(sp(j)) = min(u(sp(j)), rbuf(base + off + j))
712 end do
713 !$omp end do
714 case (gs_op_max)
715 !OCL NORECURRENCE, NOVREC, NOALIAS
716 !DIR$ CONCURRENT
717 !DIR$ IVDEP
718 !GCC$ ivdep
719 !NEC$ IVDEP
720 !$omp do
721 do j = 1, nsrc
722 u(sp(j)) = max(u(sp(j)), rbuf(base + off + j))
723 end do
724 !$omp end do
725 case default
726 call neko_error("Unknown operation in gs_nbwait_utofu")
727 end select
728 end do
729
730 nreqs = nreqs - this%ncompleted
731 ! Hold the team until every thread has read ncompleted (above) before
732 ! the master re-enters the poll and overwrites recv_indices/ncompleted.
733 !$omp barrier
734 end do
735
736 !$omp master
737 ! Flip to the other double-buffer half for the next round. Send completions
738 ! are NOT waited on here; nbsend drains the half it reuses, keeping the
739 ! send-completion wait off the critical path.
740 this%parity = 1 - this%parity
741 !$omp end master
742 !$omp barrier
743#else
744 call neko_error("uTofu support not built")
745#endif
746 end subroutine gs_nbwait_utofu
747
753 subroutine gs_nbsend_vec_utofu(this, u, n, nc, tag, deps, strm)
754 class(gs_utofu_t), intent(inout) :: this
755 integer, intent(in) :: n, nc
756 real(kind=rp), dimension(nc*n), intent(inout) :: u
757 integer, intent(in) :: tag
758 type(c_ptr), intent(inout) :: deps
759 type(c_ptr), intent(inout) :: strm
760#ifdef HAVE_UTOFU
761 integer :: i, j, c, dst, off, ndst, send_base, tid, nteam
762 integer(c_int) :: ierr
763 integer, pointer :: sp(:)
764
765 tid = 0
766 nteam = 1
767 !$ tid = omp_get_thread_num()
768 !$ nteam = omp_get_num_threads()
769
770 ! Reclaim the vector send-buffer half we are about to repack (see
771 ! gs_nbsend_utofu for the ownership rules).
772 call gs_utofu_drain_half(this%ctx_v, int(this%parity_v, c_int), &
773 int(tid, c_int), int(nteam, c_int), ierr)
774 if (ierr .ne. 0) call neko_error("gs_utofu: vector send drain failed")
775 !$omp barrier
776
777 ! Scale the scalar per-peer layout by this round's nc and fold the
778 ! vector parity into the destination offsets. Master-only; the pack
779 ! loop's implicit end-do barrier orders these writes before any thread
780 ! posts.
781 send_base = this%parity_v * (gs_vec_nc * this%send_size)
782 !$omp master
783 do i = 1, size(this%send_pe)
784 this%send_off64_v(i) = int(nc, c_int64_t) * this%send_off64(i)
785 this%send_len64_v(i) = int(nc, c_int64_t) * this%send_len64(i)
786 this%dst_off_v(i) = int(this%parity_v, c_int64_t) &
787 * (gs_vec_nc * this%rmt_bufsz_v(i)) &
788 + int(nc, c_int64_t) * this%rmt_off_v(i)
789 end do
790 !$omp end master
791
792 ! Pack each peer's slab: nc consecutive component blocks.
793 !$omp do
794 do i = 1, size(this%send_pe)
795 dst = this%send_pe(i)
796 off = this%send_offset(i)
797 ndst = this%send_len(i)
798 sp => this%send_dof(dst)%array()
799 do c = 1, nc
800 !OCL NORECURRENCE, NOVREC, NOALIAS
801 !DIR$ CONCURRENT
802 !DIR$ IVDEP
803 !GCC$ ivdep
804 !NEC$ IVDEP
805 !$omp simd
806 do j = 1, ndst
807 this%send_buf_v(send_base + nc*off + (c-1)*ndst + j) = &
808 u((c-1)*n + sp(j))
809 end do
810 end do
811 end do
812 !$omp end do
813
814 ! Fire the puts (see gs_nbsend_utofu). The same dst_off_v serves both
815 ! parity slots of the C transport -- the vector parity is already folded
816 ! into the offsets; the parity argument still stamps the edata bit.
817 call gs_utofu_post_puts(this%ctx_v, int(tid, c_int), &
818 int(nteam, c_int), int(size(this%send_pe), c_int), &
819 this%send_off64_v, this%send_len64_v, this%rmt_vcq_id_v, &
820 this%rmt_stadd_v, this%dst_off_v, this%dst_off_v, this%dst_tag, &
821 int(this%parity_v, c_int), int(send_base, c_int64_t), &
822 int(this%elem_size, c_int), ierr)
823 if (ierr .ne. 0) call neko_error("gs_utofu: vector put failed")
824 !$omp barrier
825#else
826 call neko_error("uTofu support not built")
827#endif
828 end subroutine gs_nbsend_vec_utofu
829
832 subroutine gs_nbrecv_vec_utofu(this, tag, nc)
833 class(gs_utofu_t), intent(inout) :: this
834 integer, intent(in) :: tag, nc
835 end subroutine gs_nbrecv_vec_utofu
836
840 subroutine gs_nbwait_vec_utofu(this, u, n, nc, op, strm)
841 class(gs_utofu_t), intent(inout) :: this
842 integer, intent(in) :: n, nc
843 real(kind=rp), dimension(nc*n), intent(inout) :: u
844 type(c_ptr), intent(inout) :: strm
845 integer :: op
846#ifdef HAVE_UTOFU
847 integer :: i, j, c, k, src, off, nsrc, nreqs, base
848 integer(c_int) :: ierr
849 integer, pointer :: sp(:)
850 real(kind=rp), pointer :: rbuf(:)
851
852 base = this%parity_v * (gs_vec_nc * this%buf_size)
853 nreqs = size(this%recv_pe)
854 rbuf => this%recv_buf_v
855
856 do while (nreqs .gt. 0)
857 !$omp master
858 ! Spin on the vector MRQ from the master alone (see gs_nbwait_utofu).
859 this%ncompleted = 0
860 do while (this%ncompleted .eq. 0)
861 call gs_utofu_poll_recv(this%ctx_v, int(this%parity_v, c_int), &
862 int(size(this%recv_pe), c_int), this%ncompleted, &
863 this%recv_indices, ierr)
864 if (ierr .ne. 0) &
865 call neko_error("gs_utofu: vector mrq poll failed")
866 end do
867 !$omp end master
868 !$omp barrier
869
870 do k = 1, this%ncompleted
871 i = this%recv_indices(k)
872 src = this%recv_pe(i)
873 off = this%recv_offset(i)
874 nsrc = this%recv_len(i)
875 sp => this%recv_dof(src)%array()
876 select case (op)
877 case (gs_op_add)
878 !$omp do
879 do j = 1, nsrc
880 do c = 1, nc
881 u((c-1)*n + sp(j)) = u((c-1)*n + sp(j)) + &
882 rbuf(base + nc*off + (c-1)*nsrc + j)
883 end do
884 end do
885 !$omp end do
886 case (gs_op_mul)
887 !$omp do
888 do j = 1, nsrc
889 do c = 1, nc
890 u((c-1)*n + sp(j)) = u((c-1)*n + sp(j)) * &
891 rbuf(base + nc*off + (c-1)*nsrc + j)
892 end do
893 end do
894 !$omp end do
895 case (gs_op_min)
896 !$omp do
897 do j = 1, nsrc
898 do c = 1, nc
899 u((c-1)*n + sp(j)) = min(u((c-1)*n + sp(j)), &
900 rbuf(base + nc*off + (c-1)*nsrc + j))
901 end do
902 end do
903 !$omp end do
904 case (gs_op_max)
905 !$omp do
906 do j = 1, nsrc
907 do c = 1, nc
908 u((c-1)*n + sp(j)) = max(u((c-1)*n + sp(j)), &
909 rbuf(base + nc*off + (c-1)*nsrc + j))
910 end do
911 end do
912 !$omp end do
913 case default
914 call neko_error("Unknown operation in gs_nbwait_vec_utofu")
915 end select
916 end do
917
918 nreqs = nreqs - this%ncompleted
919 ! Hold the team until every thread has read ncompleted (above) before
920 ! the master re-enters the poll and overwrites recv_indices/ncompleted.
921 !$omp barrier
922 end do
923
924 !$omp master
925 ! Flip to the other vector double-buffer half for the next vector round.
926 ! Send completions are reaped by the next nbsend_vec's drain.
927 this%parity_v = 1 - this%parity_v
928 !$omp end master
929 !$omp barrier
930#else
931 call neko_error("uTofu support not built")
932#endif
933 end subroutine gs_nbwait_vec_utofu
934
935end module gs_utofu
void gs_utofu_post_puts(void *vctx, int tid, int nteam, int nsend, const int64_t *send_off, const int64_t *send_len, const uint64_t *rmt_vcq_id, const uint64_t *rmt_stadd, const int64_t *dst_off0, const int64_t *dst_off1, const int *dst_tag, int parity, int64_t send_base, int elem_size, int *ierr)
void gs_utofu_ctx_free(void *vctx, int *ierr)
void gs_utofu_poll_recv(void *vctx, int parity, int cap, int *ncompleted, int *out_idx, int *ierr)
void gs_utofu_ctx_create(void *send_buf, size_t send_bytes, void *recv_buf, size_t recv_bytes, void **ctx_out, int *nrvcq_out, uint64_t *recv_vcq_id, uint64_t *recv_stadd, int *ierr)
void gs_utofu_drain_half(void *vctx, int half, int tid, int nteam, int *ierr)
Definition comm.F90:1
integer, public pe_rank
MPI rank.
Definition comm.F90:59
type(mpi_comm), public neko_comm
MPI communicator.
Definition comm.F90:46
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
integer, parameter, public gs_comm_utofu
Definition gs_comm.f90:43
Defines Gather-scatter operations.
Definition gs_ops.f90:34
integer, parameter, public gs_op_add
Definition gs_ops.f90:36
integer, parameter, public gs_op_max
Definition gs_ops.f90:36
integer, parameter, public gs_op_min
Definition gs_ops.f90:36
integer, parameter, public gs_op_mul
Definition gs_ops.f90:36
Defines a gather-scatter backend using the native Tofu interconnect (uTofu). Each rank registers its ...
Definition gs_utofu.F90:43
integer, parameter gs_utofu_xchg_tag
MPI tag used for the one-off neighbour metadata exchange at init.
Definition gs_utofu.F90:71
subroutine gs_utofu_init(this, send_pe, recv_pe)
Initialise the uTofu communication method. See gs_comm.f90 for details.
Definition gs_utofu.F90:224
subroutine gs_nbsend_vec_utofu(this, u, n, nc, tag, deps, strm)
Pack the fused nc-component send slabs and fire the puts (non-blocking).
Definition gs_utofu.F90:754
integer, parameter gs_utofu_xchg_tag_v
Ditto for the fused vector path's metadata exchange.
Definition gs_utofu.F90:73
subroutine gs_nbwait_vec_utofu(this, u, n, nc, op, strm)
Poll for incoming vector puts, reducing each nc-component slab into u as it lands,...
Definition gs_utofu.F90:841
subroutine gs_nbwait_utofu(this, u, n, op, strm)
Poll for incoming puts, reducing each slab into u as it lands, then wait for our own sends to drain a...
Definition gs_utofu.F90:641
logical, parameter, public gs_utofu_avail
Whether uTofu support was built into this Neko (–with-utofu). Lets callers (e.g. the gs comm....
Definition gs_utofu.F90:65
subroutine gs_nbsend_utofu(this, u, n, tag, deps, strm)
Pack the send slabs and fire all one-sided puts (non-blocking).
Definition gs_utofu.F90:558
subroutine gs_nbrecv_vec_utofu(this, tag, nc)
No-op: the vector receive buffer is registered once at init, so there is nothing to post per round.
Definition gs_utofu.F90:833
subroutine gs_nbrecv_utofu(this, tag)
No-op: the one-sided receive buffer is registered once at init, so there is nothing to post per round...
Definition gs_utofu.F90:634
subroutine gs_utofu_free(this)
Deallocate the uTofu communication method.
Definition gs_utofu.F90:445
Logging routines.
Definition log.f90:34
type(log_t), public neko_log
Global log stream.
Definition log.f90:91
integer, parameter, public log_size
Definition log.f90:46
integer, parameter, public i8
Definition num_types.f90:7
integer, parameter, public sp
Definition num_types.f90:8
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
Gather-scatter communication using one-sided uTofu puts.
Definition gs_utofu.F90:76
Integer based stack.
Definition stack.f90:77
#define max(a, b)
Definition tensor.cu:40