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