Neko 1.99.5
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 !GCC$ ivdep
591 !NEC$ IVDEP
592 !$omp simd
593 do j = 1, ndst
594 this%send_buf(send_base + off + j) = u(sp(j))
595 end do
596 end do
597 !$omp end do
598
599 ! Fire the one-sided puts: each thread injects its modulo share of the
600 ! peers on its own VCQ (or thread 0 injects everything when
601 ! NEKO_GS_UTOFU_MASTER_INJECT is set, as an A/B reference). The trailing
602 ! barrier holds the team until every put has been issued.
603 call gs_utofu_post_puts(this%ctx, int(tid, c_int), int(nteam, c_int), &
604 int(size(this%send_pe), c_int), &
605 this%send_off64, this%send_len64, this%rmt_vcq_id, this%rmt_stadd, &
606 this%dst_off0, this%dst_off1, this%dst_tag, &
607 int(this%parity, c_int), int(send_base, c_int64_t), &
608 int(this%elem_size, c_int), ierr)
609 if (ierr .ne. 0) call neko_error("gs_utofu: put failed")
610 !$omp barrier
611#else
612 call neko_error("uTofu support not built")
613#endif
614 end subroutine gs_nbsend_utofu
615
618 subroutine gs_nbrecv_utofu(this, tag)
619 class(gs_utofu_t), intent(inout) :: this
620 integer, intent(in) :: tag
621 end subroutine gs_nbrecv_utofu
622
625 subroutine gs_nbwait_utofu(this, u, n, op, strm)
626 class(gs_utofu_t), intent(inout) :: this
627 integer, intent(in) :: n
628 real(kind=rp), dimension(n), intent(inout) :: u
629 type(c_ptr), intent(inout) :: strm
630 integer :: op
631#ifdef HAVE_UTOFU
632 integer :: i, j, k, src, off, nsrc, nreqs, base
633 integer(c_int) :: ierr
634 integer, pointer :: sp(:)
635 ! Bind a local pointer to the receive buffer and index it in the unpack
636 ! loops, mirroring how the CAF backend uses its module-level buffer.
637 real(kind=rp), pointer :: rbuf(:)
638
639 base = this%parity * this%buf_size
640 nreqs = size(this%recv_pe)
641 rbuf => this%recv_buf
642
643 do while (nreqs .gt. 0)
644 !$omp master
645 ! Spin on the MRQ from the master alone until at least one slab lands.
646 ! Polling here (rather than around the whole team) keeps the idle
647 ! threads from paying a barrier on every empty poll -- the dominant
648 ! cost under interconnect latency.
649 this%ncompleted = 0
650 do while (this%ncompleted .eq. 0)
651 call gs_utofu_poll_recv(this%ctx, int(this%parity, c_int), &
652 int(size(this%recv_pe), c_int), this%ncompleted, &
653 this%recv_indices, ierr)
654 if (ierr .ne. 0) call neko_error("gs_utofu: mrq poll failed")
655 end do
656 !$omp end master
657 !$omp barrier
658
659 do k = 1, this%ncompleted
660 i = this%recv_indices(k)
661 src = this%recv_pe(i)
662 off = this%recv_offset(i)
663 nsrc = this%recv_len(i)
664 sp => this%recv_dof(src)%array()
665 select case (op)
666 case (gs_op_add)
667 !OCL NORECURRENCE, NOVREC, NOALIAS
668 !DIR$ CONCURRENT
669 !GCC$ ivdep
670 !NEC$ IVDEP
671 !$omp do
672 do j = 1, nsrc
673 u(sp(j)) = u(sp(j)) + rbuf(base + off + j)
674 end do
675 !$omp end do
676 case (gs_op_mul)
677 !OCL NORECURRENCE, NOVREC, NOALIAS
678 !DIR$ CONCURRENT
679 !GCC$ ivdep
680 !NEC$ IVDEP
681 !$omp do
682 do j = 1, nsrc
683 u(sp(j)) = u(sp(j)) * rbuf(base + off + j)
684 end do
685 !$omp end do
686 case (gs_op_min)
687 !OCL NORECURRENCE, NOVREC, NOALIAS
688 !DIR$ CONCURRENT
689 !GCC$ ivdep
690 !NEC$ IVDEP
691 !$omp do
692 do j = 1, nsrc
693 u(sp(j)) = min(u(sp(j)), rbuf(base + off + j))
694 end do
695 !$omp end do
696 case (gs_op_max)
697 !OCL NORECURRENCE, NOVREC, NOALIAS
698 !DIR$ CONCURRENT
699 !GCC$ ivdep
700 !NEC$ IVDEP
701 !$omp do
702 do j = 1, nsrc
703 u(sp(j)) = max(u(sp(j)), rbuf(base + off + j))
704 end do
705 !$omp end do
706 case default
707 call neko_error("Unknown operation in gs_nbwait_utofu")
708 end select
709 end do
710
711 nreqs = nreqs - this%ncompleted
712 ! Hold the team until every thread has read ncompleted (above) before
713 ! the master re-enters the poll and overwrites recv_indices/ncompleted.
714 !$omp barrier
715 end do
716
717 !$omp master
718 ! Flip to the other double-buffer half for the next round. Send completions
719 ! are NOT waited on here; nbsend drains the half it reuses, keeping the
720 ! send-completion wait off the critical path.
721 this%parity = 1 - this%parity
722 !$omp end master
723 !$omp barrier
724#else
725 call neko_error("uTofu support not built")
726#endif
727 end subroutine gs_nbwait_utofu
728
734 subroutine gs_nbsend_vec_utofu(this, u, n, nc, tag, deps, strm)
735 class(gs_utofu_t), intent(inout) :: this
736 integer, intent(in) :: n, nc
737 real(kind=rp), dimension(nc*n), intent(inout) :: u
738 integer, intent(in) :: tag
739 type(c_ptr), intent(inout) :: deps
740 type(c_ptr), intent(inout) :: strm
741#ifdef HAVE_UTOFU
742 integer :: i, j, c, dst, off, ndst, send_base, tid, nteam
743 integer(c_int) :: ierr
744 integer, pointer :: sp(:)
745
746 tid = 0
747 nteam = 1
748 !$ tid = omp_get_thread_num()
749 !$ nteam = omp_get_num_threads()
750
751 ! Reclaim the vector send-buffer half we are about to repack (see
752 ! gs_nbsend_utofu for the ownership rules).
753 call gs_utofu_drain_half(this%ctx_v, int(this%parity_v, c_int), &
754 int(tid, c_int), int(nteam, c_int), ierr)
755 if (ierr .ne. 0) call neko_error("gs_utofu: vector send drain failed")
756 !$omp barrier
757
758 ! Scale the scalar per-peer layout by this round's nc and fold the
759 ! vector parity into the destination offsets. Master-only; the pack
760 ! loop's implicit end-do barrier orders these writes before any thread
761 ! posts.
762 send_base = this%parity_v * (gs_vec_nc * this%send_size)
763 !$omp master
764 do i = 1, size(this%send_pe)
765 this%send_off64_v(i) = int(nc, c_int64_t) * this%send_off64(i)
766 this%send_len64_v(i) = int(nc, c_int64_t) * this%send_len64(i)
767 this%dst_off_v(i) = int(this%parity_v, c_int64_t) &
768 * (gs_vec_nc * this%rmt_bufsz_v(i)) &
769 + int(nc, c_int64_t) * this%rmt_off_v(i)
770 end do
771 !$omp end master
772
773 ! Pack each peer's slab: nc consecutive component blocks.
774 !$omp do
775 do i = 1, size(this%send_pe)
776 dst = this%send_pe(i)
777 off = this%send_offset(i)
778 ndst = this%send_len(i)
779 sp => this%send_dof(dst)%array()
780 do c = 1, nc
781 !OCL NORECURRENCE, NOVREC, NOALIAS
782 !DIR$ CONCURRENT
783 !GCC$ ivdep
784 !NEC$ IVDEP
785 !$omp simd
786 do j = 1, ndst
787 this%send_buf_v(send_base + nc*off + (c-1)*ndst + j) = &
788 u((c-1)*n + sp(j))
789 end do
790 end do
791 end do
792 !$omp end do
793
794 ! Fire the puts (see gs_nbsend_utofu). The same dst_off_v serves both
795 ! parity slots of the C transport -- the vector parity is already folded
796 ! into the offsets; the parity argument still stamps the edata bit.
797 call gs_utofu_post_puts(this%ctx_v, int(tid, c_int), &
798 int(nteam, c_int), int(size(this%send_pe), c_int), &
799 this%send_off64_v, this%send_len64_v, this%rmt_vcq_id_v, &
800 this%rmt_stadd_v, this%dst_off_v, this%dst_off_v, this%dst_tag, &
801 int(this%parity_v, c_int), int(send_base, c_int64_t), &
802 int(this%elem_size, c_int), ierr)
803 if (ierr .ne. 0) call neko_error("gs_utofu: vector put failed")
804 !$omp barrier
805#else
806 call neko_error("uTofu support not built")
807#endif
808 end subroutine gs_nbsend_vec_utofu
809
812 subroutine gs_nbrecv_vec_utofu(this, tag, nc)
813 class(gs_utofu_t), intent(inout) :: this
814 integer, intent(in) :: tag, nc
815 end subroutine gs_nbrecv_vec_utofu
816
820 subroutine gs_nbwait_vec_utofu(this, u, n, nc, op, strm)
821 class(gs_utofu_t), intent(inout) :: this
822 integer, intent(in) :: n, nc
823 real(kind=rp), dimension(nc*n), intent(inout) :: u
824 type(c_ptr), intent(inout) :: strm
825 integer :: op
826#ifdef HAVE_UTOFU
827 integer :: i, j, c, k, src, off, nsrc, nreqs, base
828 integer(c_int) :: ierr
829 integer, pointer :: sp(:)
830 real(kind=rp), pointer :: rbuf(:)
831
832 base = this%parity_v * (gs_vec_nc * this%buf_size)
833 nreqs = size(this%recv_pe)
834 rbuf => this%recv_buf_v
835
836 do while (nreqs .gt. 0)
837 !$omp master
838 ! Spin on the vector MRQ from the master alone (see gs_nbwait_utofu).
839 this%ncompleted = 0
840 do while (this%ncompleted .eq. 0)
841 call gs_utofu_poll_recv(this%ctx_v, int(this%parity_v, c_int), &
842 int(size(this%recv_pe), c_int), this%ncompleted, &
843 this%recv_indices, ierr)
844 if (ierr .ne. 0) &
845 call neko_error("gs_utofu: vector mrq poll failed")
846 end do
847 !$omp end master
848 !$omp barrier
849
850 do k = 1, this%ncompleted
851 i = this%recv_indices(k)
852 src = this%recv_pe(i)
853 off = this%recv_offset(i)
854 nsrc = this%recv_len(i)
855 sp => this%recv_dof(src)%array()
856 select case (op)
857 case (gs_op_add)
858 !$omp do
859 do j = 1, nsrc
860 do c = 1, nc
861 u((c-1)*n + sp(j)) = u((c-1)*n + sp(j)) + &
862 rbuf(base + nc*off + (c-1)*nsrc + j)
863 end do
864 end do
865 !$omp end do
866 case (gs_op_mul)
867 !$omp do
868 do j = 1, nsrc
869 do c = 1, nc
870 u((c-1)*n + sp(j)) = u((c-1)*n + sp(j)) * &
871 rbuf(base + nc*off + (c-1)*nsrc + j)
872 end do
873 end do
874 !$omp end do
875 case (gs_op_min)
876 !$omp do
877 do j = 1, nsrc
878 do c = 1, nc
879 u((c-1)*n + sp(j)) = min(u((c-1)*n + sp(j)), &
880 rbuf(base + nc*off + (c-1)*nsrc + j))
881 end do
882 end do
883 !$omp end do
884 case (gs_op_max)
885 !$omp do
886 do j = 1, nsrc
887 do c = 1, nc
888 u((c-1)*n + sp(j)) = max(u((c-1)*n + sp(j)), &
889 rbuf(base + nc*off + (c-1)*nsrc + j))
890 end do
891 end do
892 !$omp end do
893 case default
894 call neko_error("Unknown operation in gs_nbwait_vec_utofu")
895 end select
896 end do
897
898 nreqs = nreqs - this%ncompleted
899 ! Hold the team until every thread has read ncompleted (above) before
900 ! the master re-enters the poll and overwrites recv_indices/ncompleted.
901 !$omp barrier
902 end do
903
904 !$omp master
905 ! Flip to the other vector double-buffer half for the next vector round.
906 ! Send completions are reaped by the next nbsend_vec's drain.
907 this%parity_v = 1 - this%parity_v
908 !$omp end master
909 !$omp barrier
910#else
911 call neko_error("uTofu support not built")
912#endif
913 end subroutine gs_nbwait_vec_utofu
914
915end 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:58
type(mpi_comm), public neko_comm
MPI communicator.
Definition comm.F90:45
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:735
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:821
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:626
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:813
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:619
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