Neko 1.99.7
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#else
434 call neko_error("uTofu support not built; reconfigure with --with-utofu")
435#endif
436 end subroutine gs_utofu_init
437
439 subroutine gs_utofu_free(this)
440 class(gs_utofu_t), intent(inout) :: this
441#ifdef HAVE_UTOFU
442 integer(c_int) :: ierr
443
444 if (c_associated(this%ctx)) then
445 call gs_utofu_ctx_free(this%ctx, ierr)
446 this%ctx = c_null_ptr
447 end if
448
449 if (c_associated(this%ctx_v)) then
450 call gs_utofu_ctx_free(this%ctx_v, ierr)
451 this%ctx_v = c_null_ptr
452 end if
453
454 if (associated(this%send_buf)) then
455 deallocate(this%send_buf)
456 end if
457
458 if (associated(this%recv_buf)) then
459 deallocate(this%recv_buf)
460 end if
461
462 if (associated(this%send_buf_v)) then
463 deallocate(this%send_buf_v)
464 end if
465
466 if (associated(this%recv_buf_v)) then
467 deallocate(this%recv_buf_v)
468 end if
469
470 if (allocated(this%send_len)) then
471 deallocate(this%send_len)
472 end if
473
474 if (allocated(this%recv_len)) then
475 deallocate(this%recv_len)
476 end if
477
478 if (allocated(this%send_offset)) then
479 deallocate(this%send_offset)
480 end if
481
482 if (allocated(this%recv_offset)) then
483 deallocate(this%recv_offset)
484 end if
485
486 if (allocated(this%send_off64)) then
487 deallocate(this%send_off64)
488 end if
489
490 if (allocated(this%send_len64)) then
491 deallocate(this%send_len64)
492 end if
493
494 if (allocated(this%rmt_vcq_id)) then
495 deallocate(this%rmt_vcq_id)
496 end if
497
498 if (allocated(this%rmt_stadd)) then
499 deallocate(this%rmt_stadd)
500 end if
501
502 if (allocated(this%dst_off0)) then
503 deallocate(this%dst_off0)
504 end if
505
506 if (allocated(this%dst_off1)) then
507 deallocate(this%dst_off1)
508 end if
509
510 if (allocated(this%dst_tag)) then
511 deallocate(this%dst_tag)
512 end if
513
514 if (allocated(this%recv_indices)) then
515 deallocate(this%recv_indices)
516 end if
517
518 if (allocated(this%rmt_vcq_id_v)) then
519 deallocate(this%rmt_vcq_id_v)
520 end if
521
522 if (allocated(this%rmt_stadd_v)) then
523 deallocate(this%rmt_stadd_v)
524 end if
525
526 if (allocated(this%rmt_off_v)) then
527 deallocate(this%rmt_off_v)
528 end if
529
530 if (allocated(this%rmt_bufsz_v)) then
531 deallocate(this%rmt_bufsz_v)
532 end if
533
534 if (allocated(this%send_off64_v)) then
535 deallocate(this%send_off64_v)
536 end if
537
538 if (allocated(this%send_len64_v)) then
539 deallocate(this%send_len64_v)
540 end if
541
542 if (allocated(this%dst_off_v)) then
543 deallocate(this%dst_off_v)
544 end if
545
546 call this%free_order()
547 call this%free_dofs()
548#endif
549 end subroutine gs_utofu_free
550
552 subroutine gs_nbsend_utofu(this, u, n, tag, deps, strm)
553 class(gs_utofu_t), intent(inout) :: this
554 integer, intent(in) :: n
555 real(kind=rp), dimension(n), intent(inout) :: u
556 integer, intent(in) :: tag
557 type(c_ptr), intent(inout) :: deps
558 type(c_ptr), intent(inout) :: strm
559#ifdef HAVE_UTOFU
560 integer :: i, j, dst, off, ndst, send_base, tid, nteam
561 integer(c_int) :: ierr
562 integer, pointer :: sp(:)
563
564 ! Entered from inside the gs_op_vector OpenMP parallel region, and every
565 ! phase is work-shared: each thread owns one injection VCQ (created
566 ! THREAD_SAFE) and fires the puts for its share of the peers itself --
567 ! the uTofu substitute for the per-thread MPI_Isend that Fujitsu MPI's
568 ! missing MPI_THREAD_MULTIPLE support rules out.
569 tid = 0
570 nteam = 1
571 !$ tid = omp_get_thread_num()
572 !$ nteam = omp_get_num_threads()
573
574 ! Reclaim the send-buffer half we are about to repack: the team strides
575 ! over the injection VCQs so each one is drained by exactly one thread
576 ! (no atomics on the completion counters). The half was last used two
577 ! rounds ago, so its puts have long completed -- this just reaps their
578 ! TCQ notices. The barrier then guarantees the whole half is free before
579 ! anyone packs into it.
580 call gs_utofu_drain_half(this%ctx, int(this%parity, c_int), &
581 int(tid, c_int), int(nteam, c_int), ierr)
582 if (ierr .ne. 0) call neko_error("gs_utofu: send drain failed")
583 !$omp barrier
584
585 ! Pack each peer's slab in one work-shared loop. Subroutine locals are
586 ! per-thread private (orphaned region); the implicit barrier at end do
587 ! guarantees every slab is packed before any thread fires its puts (a
588 ! peer packed by one thread may be sent by another -- the pack and post
589 ! partitions differ).
590 send_base = this%parity * this%send_size
591 !$omp do
592 do i = 1, size(this%send_pe)
593 dst = this%send_pe(i)
594 off = this%send_offset(i)
595 ndst = this%send_len(i)
596 sp => this%send_dof(dst)%array()
597 !OCL NORECURRENCE, NOVREC, NOALIAS
598 !DIR$ CONCURRENT
599 !DIR$ IVDEP
600 !GCC$ ivdep
601 !NEC$ IVDEP
602 !$omp simd
603 do j = 1, ndst
604 this%send_buf(send_base + off + j) = u(sp(j))
605 end do
606 end do
607 !$omp end do
608
609 ! Fire the one-sided puts: each thread injects its modulo share of the
610 ! peers on its own VCQ (or thread 0 injects everything when
611 ! NEKO_GS_UTOFU_MASTER_INJECT is set, as an A/B reference). The trailing
612 ! barrier holds the team until every put has been issued.
613 call gs_utofu_post_puts(this%ctx, int(tid, c_int), int(nteam, c_int), &
614 int(size(this%send_pe), c_int), &
615 this%send_off64, this%send_len64, this%rmt_vcq_id, this%rmt_stadd, &
616 this%dst_off0, this%dst_off1, this%dst_tag, &
617 int(this%parity, c_int), int(send_base, c_int64_t), &
618 int(this%elem_size, c_int), ierr)
619 if (ierr .ne. 0) call neko_error("gs_utofu: put failed")
620 !$omp barrier
621#else
622 call neko_error("uTofu support not built")
623#endif
624 end subroutine gs_nbsend_utofu
625
628 subroutine gs_nbrecv_utofu(this, tag)
629 class(gs_utofu_t), intent(inout) :: this
630 integer, intent(in) :: tag
631 end subroutine gs_nbrecv_utofu
632
635 subroutine gs_nbwait_utofu(this, u, n, op, strm)
636 class(gs_utofu_t), intent(inout) :: this
637 integer, intent(in) :: n
638 real(kind=rp), dimension(n), intent(inout) :: u
639 type(c_ptr), intent(inout) :: strm
640 integer :: op
641#ifdef HAVE_UTOFU
642 integer :: i, j, k, src, off, nsrc, nreqs, base
643 integer(c_int) :: ierr
644 integer, pointer :: sp(:)
645 ! Bind a local pointer to the receive buffer and index it in the unpack
646 ! loops, mirroring how the CAF backend uses its module-level buffer.
647 real(kind=rp), pointer :: rbuf(:)
648
649 base = this%parity * this%buf_size
650 nreqs = size(this%recv_pe)
651 rbuf => this%recv_buf
652
653 do while (nreqs .gt. 0)
654 !$omp master
655 ! Spin on the MRQ from the master alone until at least one slab lands.
656 ! Polling here (rather than around the whole team) keeps the idle
657 ! threads from paying a barrier on every empty poll -- the dominant
658 ! cost under interconnect latency.
659 this%ncompleted = 0
660 do while (this%ncompleted .eq. 0)
661 call gs_utofu_poll_recv(this%ctx, int(this%parity, c_int), &
662 int(size(this%recv_pe), c_int), this%ncompleted, &
663 this%recv_indices, ierr)
664 if (ierr .ne. 0) call neko_error("gs_utofu: mrq poll failed")
665 end do
666 !$omp end master
667 !$omp barrier
668
669 do k = 1, this%ncompleted
670 i = this%recv_indices(k)
671 src = this%recv_pe(i)
672 off = this%recv_offset(i)
673 nsrc = this%recv_len(i)
674 sp => this%recv_dof(src)%array()
675 select case (op)
676 case (gs_op_add)
677 !OCL NORECURRENCE, NOVREC, NOALIAS
678 !DIR$ CONCURRENT
679 !DIR$ IVDEP
680 !GCC$ ivdep
681 !NEC$ IVDEP
682 !$omp do
683 do j = 1, nsrc
684 u(sp(j)) = u(sp(j)) + rbuf(base + off + j)
685 end do
686 !$omp end do
687 case (gs_op_mul)
688 !OCL NORECURRENCE, NOVREC, NOALIAS
689 !DIR$ CONCURRENT
690 !DIR$ IVDEP
691 !GCC$ ivdep
692 !NEC$ IVDEP
693 !$omp do
694 do j = 1, nsrc
695 u(sp(j)) = u(sp(j)) * rbuf(base + off + j)
696 end do
697 !$omp end do
698 case (gs_op_min)
699 !OCL NORECURRENCE, NOVREC, NOALIAS
700 !DIR$ CONCURRENT
701 !DIR$ IVDEP
702 !GCC$ ivdep
703 !NEC$ IVDEP
704 !$omp do
705 do j = 1, nsrc
706 u(sp(j)) = min(u(sp(j)), rbuf(base + off + j))
707 end do
708 !$omp end do
709 case (gs_op_max)
710 !OCL NORECURRENCE, NOVREC, NOALIAS
711 !DIR$ CONCURRENT
712 !DIR$ IVDEP
713 !GCC$ ivdep
714 !NEC$ IVDEP
715 !$omp do
716 do j = 1, nsrc
717 u(sp(j)) = max(u(sp(j)), rbuf(base + off + j))
718 end do
719 !$omp end do
720 case default
721 call neko_error("Unknown operation in gs_nbwait_utofu")
722 end select
723 end do
724
725 nreqs = nreqs - this%ncompleted
726 ! Hold the team until every thread has read ncompleted (above) before
727 ! the master re-enters the poll and overwrites recv_indices/ncompleted.
728 !$omp barrier
729 end do
730
731 !$omp master
732 ! Flip to the other double-buffer half for the next round. Send completions
733 ! are NOT waited on here; nbsend drains the half it reuses, keeping the
734 ! send-completion wait off the critical path.
735 this%parity = 1 - this%parity
736 !$omp end master
737 !$omp barrier
738#else
739 call neko_error("uTofu support not built")
740#endif
741 end subroutine gs_nbwait_utofu
742
748 subroutine gs_nbsend_vec_utofu(this, u, n, nc, tag, deps, strm)
749 class(gs_utofu_t), intent(inout) :: this
750 integer, intent(in) :: n, nc
751 real(kind=rp), dimension(nc*n), intent(inout) :: u
752 integer, intent(in) :: tag
753 type(c_ptr), intent(inout) :: deps
754 type(c_ptr), intent(inout) :: strm
755#ifdef HAVE_UTOFU
756 integer :: i, j, c, dst, off, ndst, send_base, tid, nteam
757 integer(c_int) :: ierr
758 integer, pointer :: sp(:)
759
760 tid = 0
761 nteam = 1
762 !$ tid = omp_get_thread_num()
763 !$ nteam = omp_get_num_threads()
764
765 ! Reclaim the vector send-buffer half we are about to repack (see
766 ! gs_nbsend_utofu for the ownership rules).
767 call gs_utofu_drain_half(this%ctx_v, int(this%parity_v, c_int), &
768 int(tid, c_int), int(nteam, c_int), ierr)
769 if (ierr .ne. 0) call neko_error("gs_utofu: vector send drain failed")
770 !$omp barrier
771
772 ! Scale the scalar per-peer layout by this round's nc and fold the
773 ! vector parity into the destination offsets. Master-only; the pack
774 ! loop's implicit end-do barrier orders these writes before any thread
775 ! posts.
776 send_base = this%parity_v * (gs_vec_nc * this%send_size)
777 !$omp master
778 do i = 1, size(this%send_pe)
779 this%send_off64_v(i) = int(nc, c_int64_t) * this%send_off64(i)
780 this%send_len64_v(i) = int(nc, c_int64_t) * this%send_len64(i)
781 this%dst_off_v(i) = int(this%parity_v, c_int64_t) &
782 * (gs_vec_nc * this%rmt_bufsz_v(i)) &
783 + int(nc, c_int64_t) * this%rmt_off_v(i)
784 end do
785 !$omp end master
786
787 ! Pack each peer's slab: nc consecutive component blocks.
788 !$omp do
789 do i = 1, size(this%send_pe)
790 dst = this%send_pe(i)
791 off = this%send_offset(i)
792 ndst = this%send_len(i)
793 sp => this%send_dof(dst)%array()
794 do c = 1, nc
795 !OCL NORECURRENCE, NOVREC, NOALIAS
796 !DIR$ CONCURRENT
797 !DIR$ IVDEP
798 !GCC$ ivdep
799 !NEC$ IVDEP
800 !$omp simd
801 do j = 1, ndst
802 this%send_buf_v(send_base + nc*off + (c-1)*ndst + j) = &
803 u((c-1)*n + sp(j))
804 end do
805 end do
806 end do
807 !$omp end do
808
809 ! Fire the puts (see gs_nbsend_utofu). The same dst_off_v serves both
810 ! parity slots of the C transport -- the vector parity is already folded
811 ! into the offsets; the parity argument still stamps the edata bit.
812 call gs_utofu_post_puts(this%ctx_v, int(tid, c_int), &
813 int(nteam, c_int), int(size(this%send_pe), c_int), &
814 this%send_off64_v, this%send_len64_v, this%rmt_vcq_id_v, &
815 this%rmt_stadd_v, this%dst_off_v, this%dst_off_v, this%dst_tag, &
816 int(this%parity_v, c_int), int(send_base, c_int64_t), &
817 int(this%elem_size, c_int), ierr)
818 if (ierr .ne. 0) call neko_error("gs_utofu: vector put failed")
819 !$omp barrier
820#else
821 call neko_error("uTofu support not built")
822#endif
823 end subroutine gs_nbsend_vec_utofu
824
827 subroutine gs_nbrecv_vec_utofu(this, tag, nc)
828 class(gs_utofu_t), intent(inout) :: this
829 integer, intent(in) :: tag, nc
830 end subroutine gs_nbrecv_vec_utofu
831
835 subroutine gs_nbwait_vec_utofu(this, u, n, nc, op, strm)
836 class(gs_utofu_t), intent(inout) :: this
837 integer, intent(in) :: n, nc
838 real(kind=rp), dimension(nc*n), intent(inout) :: u
839 type(c_ptr), intent(inout) :: strm
840 integer :: op
841#ifdef HAVE_UTOFU
842 integer :: i, j, c, k, src, off, nsrc, nreqs, base
843 integer(c_int) :: ierr
844 integer, pointer :: sp(:)
845 real(kind=rp), pointer :: rbuf(:)
846
847 base = this%parity_v * (gs_vec_nc * this%buf_size)
848 nreqs = size(this%recv_pe)
849 rbuf => this%recv_buf_v
850
851 do while (nreqs .gt. 0)
852 !$omp master
853 ! Spin on the vector MRQ from the master alone (see gs_nbwait_utofu).
854 this%ncompleted = 0
855 do while (this%ncompleted .eq. 0)
856 call gs_utofu_poll_recv(this%ctx_v, int(this%parity_v, c_int), &
857 int(size(this%recv_pe), c_int), this%ncompleted, &
858 this%recv_indices, ierr)
859 if (ierr .ne. 0) &
860 call neko_error("gs_utofu: vector mrq poll failed")
861 end do
862 !$omp end master
863 !$omp barrier
864
865 do k = 1, this%ncompleted
866 i = this%recv_indices(k)
867 src = this%recv_pe(i)
868 off = this%recv_offset(i)
869 nsrc = this%recv_len(i)
870 sp => this%recv_dof(src)%array()
871 select case (op)
872 case (gs_op_add)
873 !$omp do
874 do j = 1, nsrc
875 do c = 1, nc
876 u((c-1)*n + sp(j)) = u((c-1)*n + sp(j)) + &
877 rbuf(base + nc*off + (c-1)*nsrc + j)
878 end do
879 end do
880 !$omp end do
881 case (gs_op_mul)
882 !$omp do
883 do j = 1, nsrc
884 do c = 1, nc
885 u((c-1)*n + sp(j)) = u((c-1)*n + sp(j)) * &
886 rbuf(base + nc*off + (c-1)*nsrc + j)
887 end do
888 end do
889 !$omp end do
890 case (gs_op_min)
891 !$omp do
892 do j = 1, nsrc
893 do c = 1, nc
894 u((c-1)*n + sp(j)) = min(u((c-1)*n + sp(j)), &
895 rbuf(base + nc*off + (c-1)*nsrc + j))
896 end do
897 end do
898 !$omp end do
899 case (gs_op_max)
900 !$omp do
901 do j = 1, nsrc
902 do c = 1, nc
903 u((c-1)*n + sp(j)) = max(u((c-1)*n + sp(j)), &
904 rbuf(base + nc*off + (c-1)*nsrc + j))
905 end do
906 end do
907 !$omp end do
908 case default
909 call neko_error("Unknown operation in gs_nbwait_vec_utofu")
910 end select
911 end do
912
913 nreqs = nreqs - this%ncompleted
914 ! Hold the team until every thread has read ncompleted (above) before
915 ! the master re-enters the poll and overwrites recv_indices/ncompleted.
916 !$omp barrier
917 end do
918
919 !$omp master
920 ! Flip to the other vector double-buffer half for the next vector round.
921 ! Send completions are reaped by the next nbsend_vec's drain.
922 this%parity_v = 1 - this%parity_v
923 !$omp end master
924 !$omp barrier
925#else
926 call neko_error("uTofu support not built")
927#endif
928 end subroutine gs_nbwait_vec_utofu
929
930end 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:49
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:749
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:836
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:636
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:553
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:828
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:629
subroutine gs_utofu_free(this)
Deallocate the uTofu communication method.
Definition gs_utofu.F90:440
Logging routines.
Definition log.f90:34
type(log_t), public neko_log
Global log stream.
Definition log.f90:80
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:12
Implements a dynamic stack ADT.
Definition stack.f90:49
Utilities.
Definition utils.f90:35
Gather-scatter communication method.
Definition gs_comm.f90:52
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