Neko 1.99.9
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
gs_device_nccl.F90
Go to the documentation of this file.
1! Copyright (c) 2025, The Neko Authors
2! All rights reserved.
3!
4! Redistribution and use in source and binary forms, with or without
5! modification, are permitted provided that the following conditions
6! are met:
7!
8! * Redistributions of source code must retain the above copyright
9! notice, this list of conditions and the following disclaimer.
10!
11! * Redistributions in binary form must reproduce the above
12! copyright notice, this list of conditions and the following
13! disclaimer in the documentation and/or other materials provided
14! with the distribution.
15!
16! * Neither the name of the authors nor the names of its
17! contributors may be used to endorse or promote products derived
18! from this software without specific prior written permission.
19!
20! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21! "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22! LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23! FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24! COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25! INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26! BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27! LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30! ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31! POSSIBILITY OF SUCH DAMAGE.
32!
35 use num_types, only : rp, c_rp
36 use gs_comm, only : gs_comm_t, gs_vec_nc
37 use stack, only : stack_i4_t
38 use comm, only : pe_size, pe_rank
39 use htable, only : htable_i4_t
44 use utils, only : neko_error
45 use, intrinsic :: iso_c_binding, only : c_sizeof, c_int32_t, &
46 c_ptr, c_null_ptr, c_size_t, c_associated, c_int
47 implicit none
48 private
49
53#if defined(HAVE_NCCL) || defined(HAVE_RCCL)
54 logical, parameter, public :: GS_DEVICE_NCCL_AVAIL = .true.
55#else
56 logical, parameter, public :: gs_device_nccl_avail = .false.
57#endif
58
60 type, private :: gs_device_nccl_buf_t
61 integer, allocatable :: ndofs(:)
62 integer, allocatable :: offset(:)
63 integer :: total
64 type(c_ptr) :: buf_d = c_null_ptr
65 type(c_ptr) :: buf_v_d = c_null_ptr
66 type(c_ptr) :: dof_d = c_null_ptr
67 contains
68 procedure, pass(this) :: init => gs_device_nccl_buf_init
69 procedure, pass(this) :: free => gs_device_nccl_buf_free
71
74 type, public, extends(gs_comm_t) :: gs_device_nccl_t
75 type(gs_device_nccl_buf_t) :: send_buf
76 type(gs_device_nccl_buf_t) :: recv_buf
77 type(c_ptr), allocatable :: stream(:)
78 type(c_ptr), allocatable :: event(:)
79 integer :: nb_strtgy
80 type(c_ptr) :: send_event = c_null_ptr
81 contains
82 procedure, pass(this) :: init => gs_device_nccl_init
83 procedure, pass(this) :: free => gs_device_nccl_free
84 procedure, pass(this) :: nbsend => gs_device_nccl_nbsend
85 procedure, pass(this) :: nbrecv => gs_device_nccl_nbrecv
86 procedure, pass(this) :: nbwait => gs_device_nccl_nbwait
87 procedure, pass(this) :: nbsend_vec => gs_device_nccl_nbsend_vec
88 procedure, pass(this) :: nbrecv_vec => gs_device_nccl_nbrecv_vec
89 procedure, pass(this) :: nbwait_vec => gs_device_nccl_nbwait_vec
90 end type gs_device_nccl_t
91
92#ifdef HAVE_HIP
93 interface
94 subroutine hip_gs_pack(u_d, buf_d, dof_d, offset, n, stream) &
95 bind(c, name = 'hip_gs_pack')
96 use, intrinsic :: iso_c_binding
97 implicit none
98 integer(c_int), value :: n, offset
99 type(c_ptr), value :: u_d, buf_d, dof_d, stream
100 end subroutine hip_gs_pack
101 end interface
102
103 interface
104 subroutine hip_gs_unpack(u_d, op, buf_d, dof_d, offset, n, stream) &
105 bind(c, name = 'hip_gs_unpack')
106 use, intrinsic :: iso_c_binding
107 implicit none
108 integer(c_int), value :: op, offset, n
109 type(c_ptr), value :: u_d, buf_d, dof_d, stream
110 end subroutine hip_gs_unpack
111 end interface
112
113 interface
114 subroutine hip_gs_pack_vec(u_d, buf_d, dof_d, offset, n, nc, ns, stream) &
115 bind(c, name = 'hip_gs_pack_vec')
116 use, intrinsic :: iso_c_binding
117 implicit none
118 integer(c_int), value :: offset, n, nc, ns
119 type(c_ptr), value :: u_d, buf_d, dof_d, stream
120 end subroutine hip_gs_pack_vec
121 end interface
122
123 interface
124 subroutine hip_gs_unpack_vec(u_d, op, buf_d, dof_d, offset, n, nc, ns, &
125 stream) bind(c, name = 'hip_gs_unpack_vec')
126 use, intrinsic :: iso_c_binding
127 implicit none
128 integer(c_int), value :: op, offset, n, nc, ns
129 type(c_ptr), value :: u_d, buf_d, dof_d, stream
130 end subroutine hip_gs_unpack_vec
131 end interface
132#elif HAVE_CUDA
133 interface
134 subroutine cuda_gs_pack(u_d, buf_d, dof_d, offset, n, stream) &
135 bind(c, name = 'cuda_gs_pack')
136 use, intrinsic :: iso_c_binding
137 implicit none
138 integer(c_int), value :: n, offset
139 type(c_ptr), value :: u_d, buf_d, dof_d, stream
140 end subroutine cuda_gs_pack
141 end interface
142
143 interface
144 subroutine cuda_gs_unpack(u_d, op, buf_d, dof_d, offset, n, stream) &
145 bind(c, name = 'cuda_gs_unpack')
146 use, intrinsic :: iso_c_binding
147 implicit none
148 integer(c_int), value :: op, offset, n
149 type(c_ptr), value :: u_d, buf_d, dof_d, stream
150 end subroutine cuda_gs_unpack
151 end interface
152
153 interface
154 subroutine cuda_gs_pack_vec(u_d, buf_d, dof_d, offset, n, nc, ns, stream) &
155 bind(c, name = 'cuda_gs_pack_vec')
156 use, intrinsic :: iso_c_binding
157 implicit none
158 integer(c_int), value :: offset, n, nc, ns
159 type(c_ptr), value :: u_d, buf_d, dof_d, stream
160 end subroutine cuda_gs_pack_vec
161 end interface
162
163 interface
164 subroutine cuda_gs_unpack_vec(u_d, op, buf_d, dof_d, offset, n, nc, ns, &
165 stream) bind(c, name = 'cuda_gs_unpack_vec')
166 use, intrinsic :: iso_c_binding
167 implicit none
168 integer(c_int), value :: op, offset, n, nc, ns
169 type(c_ptr), value :: u_d, buf_d, dof_d, stream
170 end subroutine cuda_gs_unpack_vec
171 end interface
172#endif
173
174 interface
175 subroutine device_nccl_sendrecv(sbuf_d, soffset, scount, srank, &
176 rbuf_d, roffset, rcount, rrank, nbytes, stream) &
177 bind(c, name = 'device_nccl_sendrecv')
178 use, intrinsic :: iso_c_binding
179 implicit none
180 integer(c_int), value :: soffset, scount, roffset, rcount
181 integer(c_int), value :: srank, rrank, nbytes
182 type(c_ptr), value :: sbuf_d, rbuf_d, stream
183 end subroutine device_nccl_sendrecv
184 end interface
185
186contains
187
188 subroutine gs_device_nccl_buf_init(this, pe_order, dof_stack, mark_dupes)
189 class(gs_device_nccl_buf_t), intent(inout) :: this
190 integer, allocatable, intent(inout) :: pe_order(:)
191 type(stack_i4_t), allocatable, intent(inout) :: dof_stack(:)
192 logical, intent(in) :: mark_dupes
193 integer, allocatable :: dofs(:)
194 integer :: i, j, total
195 integer(c_size_t) :: sz
196 type(htable_i4_t) :: doftable
197 integer :: dupe, marked, k
198 real(c_rp) :: rp_dummy
199 integer(c_int32_t) :: i4_dummy
200
201
202 allocate(this%ndofs(size(pe_order)))
203 allocate(this%offset(size(pe_order)))
204
205 total = 0
206 do i = 1, size(pe_order)
207 this%ndofs(i) = dof_stack(pe_order(i))%size()
208 this%offset(i) = total
209 total = total + this%ndofs(i)
210 end do
211
212 this%total = total
213
214 sz = c_sizeof(rp_dummy) * total
215 call device_alloc(this%buf_d, sz)
216
217 ! Fused vector buffer, sized for up to GS_VEC_NC components.
218 sz = c_sizeof(rp_dummy) * gs_vec_nc * total
219 call device_alloc(this%buf_v_d, sz)
220
221 sz = c_sizeof(i4_dummy) * total
222 call device_alloc(this%dof_d, sz)
223
224 if (mark_dupes) call doftable%init(2*total)
225 allocate(dofs(total))
226
227 ! Copy from dof_stack into dofs, optionally marking duplicates with doftable
228 marked = 0
229 do i = 1, size(pe_order)
230 ! %array() breaks on cray
231 select type (arr => dof_stack(pe_order(i))%data)
232 type is (integer)
233 do j = 1, this%ndofs(i)
234 k = this%offset(i) + j
235 if (mark_dupes) then
236 if (doftable%get(arr(j), dupe) .eq. 0) then
237 if (dofs(dupe) .gt. 0) then
238 dofs(dupe) = -dofs(dupe)
239 marked = marked + 1
240 end if
241 dofs(k) = -arr(j)
242 marked = marked + 1
243 else
244 call doftable%set(arr(j), k)
245 dofs(k) = arr(j)
246 end if
247 else
248 dofs(k) = arr(j)
249 end if
250 end do
251 end select
252 end do
253
254 call device_memcpy(dofs, this%dof_d, total, host_to_device, sync = .true.)
255
256 deallocate(dofs)
257 call doftable%free()
258
259 end subroutine gs_device_nccl_buf_init
260
261 subroutine gs_device_nccl_buf_free(this)
262 class(gs_device_nccl_buf_t), intent(inout) :: this
263
264 if (allocated(this%ndofs)) deallocate(this%ndofs)
265 if (allocated(this%offset)) deallocate(this%offset)
266
267 if (c_associated(this%buf_d)) call device_free(this%buf_d)
268 if (c_associated(this%buf_v_d)) call device_free(this%buf_v_d)
269 if (c_associated(this%dof_d)) call device_free(this%dof_d)
270 end subroutine gs_device_nccl_buf_free
271
273 subroutine gs_device_nccl_init(this, send_pe, recv_pe)
274 class(gs_device_nccl_t), intent(inout) :: this
275 type(stack_i4_t), intent(inout) :: send_pe
276 type(stack_i4_t), intent(inout) :: recv_pe
277 integer :: i, nstrm
278
279#if !defined(HAVE_NCCL) && !defined(HAVE_RCCL)
280 call neko_error('Neko was not built with NCCL support')
281#endif
282
283 call this%init_order(send_pe, recv_pe)
284
285 call this%send_buf%init(this%send_pe, this%send_dof, .false.)
286 call this%recv_buf%init(this%recv_pe, this%recv_dof, .true.)
287
288#if defined(HAVE_HIP) || defined(HAVE_CUDA)
289 ! Create a set of non-blocking streams. The per-peer streams and events
290 ! are indexed over both send_pe (pack, sendrecv) and recv_pe (unpack,
291 ! sync), so size them for the larger of the two peer lists.
292 nstrm = max(size(this%send_pe), size(this%recv_pe))
293 allocate(this%stream(nstrm))
294 do i = 1, nstrm
295 call device_stream_create_with_priority(this%stream(i), 1, &
297 end do
298
299 allocate(this%event(nstrm))
300 do i = 1, nstrm
301 call device_event_create(this%event(i), 2)
302 end do
303#endif
304
305 this%vec_supported = .true.
306
307 end subroutine gs_device_nccl_init
308
310 subroutine gs_device_nccl_free(this)
311 class(gs_device_nccl_t), intent(inout) :: this
312 integer :: i
313
314 call this%send_buf%free()
315 call this%recv_buf%free()
316
317 call this%free_order()
318 call this%free_dofs()
319
320#if defined(HAVE_HIP) || defined(HAVE_CUDA)
321 if (allocated(this%stream)) then
322 do i = 1, size(this%stream)
323 call device_stream_destroy(this%stream(i))
324 end do
325 deallocate(this%stream)
326 end if
327
328 if (allocated(this%event)) then
329 do i = 1, size(this%event)
330 call device_event_destroy(this%event(i))
331 end do
332 deallocate(this%event)
333 end if
334#endif
335
336 end subroutine gs_device_nccl_free
337
339 subroutine gs_device_nccl_nbsend(this, u, n, tag, deps, strm)
340 class(gs_device_nccl_t), intent(inout) :: this
341 integer, intent(in) :: n
342 real(kind=rp), dimension(n), intent(inout) :: u
343 integer, intent(in) :: tag
344 type(c_ptr), intent(inout) :: deps
345 type(c_ptr), intent(inout) :: strm
346 integer :: i
347 type(c_ptr) :: u_d
348
349 u_d = device_get_ptr(u)
350
351 do i = 1, size(this%send_pe)
352 call device_stream_wait_event(this%stream(i), deps, 0)
353#ifdef HAVE_HIP
354 call hip_gs_pack(u_d, &
355 this%send_buf%buf_d, &
356 this%send_buf%dof_d, &
357 this%send_buf%offset(i), &
358 this%send_buf%ndofs(i), &
359 this%stream(i))
360#elif HAVE_CUDA
361 call cuda_gs_pack(u_d, &
362 this%send_buf%buf_d, &
363 this%send_buf%dof_d, &
364 this%send_buf%offset(i), &
365 this%send_buf%ndofs(i), &
366 this%stream(i))
367#else
368 call neko_error('gs_device_nccl: no backend')
369#endif
370 end do
371
372
373 ! Everything else is done in the wait routine
374
375 end subroutine gs_device_nccl_nbsend
376
378 subroutine gs_device_nccl_nbrecv(this, tag)
379 class(gs_device_nccl_t), intent(inout) :: this
380 integer, intent(in) :: tag
381 integer :: i
382
383 ! Everything is done in the wait routine
384
385 end subroutine gs_device_nccl_nbrecv
386
388 subroutine gs_device_nccl_nbwait(this, u, n, op, strm)
389 class(gs_device_nccl_t), intent(inout) :: this
390 integer, intent(in) :: n
391 real(kind=rp), dimension(n), intent(inout) :: u
392 type(c_ptr), intent(inout) :: strm
393 integer :: op, done_req, i
394 type(c_ptr) :: u_d
395 real(c_rp) :: rp_dummy
396 integer(c_int) :: nbytes
397
398 u_d = device_get_ptr(u)
399 nbytes = c_sizeof(rp_dummy)
400
401 do i = 1, size(this%send_pe)
402
403 call device_nccl_sendrecv(this%send_buf%buf_d, &
404 nbytes*this%send_buf%offset(i), &
405 this%send_buf%ndofs(i), &
406 this%send_pe(i), &
407 this%recv_buf%buf_d, &
408 nbytes*this%recv_buf%offset(i), &
409 this%recv_buf%ndofs(i), &
410 this%recv_pe(i), &
411 nbytes, &
412 this%stream(i))
413
414#ifdef HAVE_HIP
415 call hip_gs_unpack(u_d, op, &
416 this%recv_buf%buf_d, &
417 this%recv_buf%dof_d, &
418 this%recv_buf%offset(i), &
419 this%recv_buf%ndofs(i), &
420 this%stream(i))
421#elif HAVE_CUDA
422 call cuda_gs_unpack(u_d, op, &
423 this%recv_buf%buf_d, &
424 this%recv_buf%dof_d, &
425 this%recv_buf%offset(i), &
426 this%recv_buf%ndofs(i), &
427 this%stream(i))
428#else
429 call neko_error('gs_device_mpi: no backend')
430#endif
431 call device_event_record(this%event(i), this%stream(i))
432 end do
433
434 ! Sync non-blocking streams
435 do done_req = 1, size(this%recv_pe)
436 call device_stream_wait_event(strm, &
437 this%event(done_req), 0)
438 end do
439
440 end subroutine gs_device_nccl_nbwait
441
445 subroutine gs_device_nccl_nbsend_vec(this, u, n, nc, tag, deps, strm)
446 class(gs_device_nccl_t), intent(inout) :: this
447 integer, intent(in) :: n, nc
448 real(kind=rp), dimension(nc*n), intent(inout) :: u
449 integer, intent(in) :: tag
450 type(c_ptr), intent(inout) :: deps
451 type(c_ptr), intent(inout) :: strm
452 integer :: i
453 type(c_ptr) :: u_d
454
455 u_d = device_get_ptr(u)
456
457 do i = 1, size(this%send_pe)
458 call device_stream_wait_event(this%stream(i), deps, 0)
459#ifdef HAVE_HIP
460 call hip_gs_pack_vec(u_d, this%send_buf%buf_v_d, this%send_buf%dof_d, &
461 this%send_buf%offset(i), this%send_buf%ndofs(i), nc, n, &
462 this%stream(i))
463#elif HAVE_CUDA
464 call cuda_gs_pack_vec(u_d, this%send_buf%buf_v_d, this%send_buf%dof_d, &
465 this%send_buf%offset(i), this%send_buf%ndofs(i), nc, n, &
466 this%stream(i))
467#else
468 call neko_error('gs_device_nccl: no backend')
469#endif
470 end do
471
472 end subroutine gs_device_nccl_nbsend_vec
473
475 subroutine gs_device_nccl_nbrecv_vec(this, tag, nc)
476 class(gs_device_nccl_t), intent(inout) :: this
477 integer, intent(in) :: tag, nc
478 end subroutine gs_device_nccl_nbrecv_vec
479
481 subroutine gs_device_nccl_nbwait_vec(this, u, n, nc, op, strm)
482 class(gs_device_nccl_t), intent(inout) :: this
483 integer, intent(in) :: n, nc
484 real(kind=rp), dimension(nc*n), intent(inout) :: u
485 type(c_ptr), intent(inout) :: strm
486 integer :: op, done_req, i
487 type(c_ptr) :: u_d
488 real(c_rp) :: rp_dummy
489 integer(c_int) :: nbytes
490
491 u_d = device_get_ptr(u)
492 nbytes = c_sizeof(rp_dummy)
493
494 do i = 1, size(this%send_pe)
495
496 call device_nccl_sendrecv(this%send_buf%buf_v_d, &
497 nbytes*nc*this%send_buf%offset(i), &
498 nc*this%send_buf%ndofs(i), &
499 this%send_pe(i), &
500 this%recv_buf%buf_v_d, &
501 nbytes*nc*this%recv_buf%offset(i), &
502 nc*this%recv_buf%ndofs(i), &
503 this%recv_pe(i), &
504 nbytes, &
505 this%stream(i))
506
507#ifdef HAVE_HIP
508 call hip_gs_unpack_vec(u_d, op, this%recv_buf%buf_v_d, &
509 this%recv_buf%dof_d, this%recv_buf%offset(i), &
510 this%recv_buf%ndofs(i), nc, n, this%stream(i))
511#elif HAVE_CUDA
512 call cuda_gs_unpack_vec(u_d, op, this%recv_buf%buf_v_d, &
513 this%recv_buf%dof_d, this%recv_buf%offset(i), &
514 this%recv_buf%ndofs(i), nc, n, this%stream(i))
515#else
516 call neko_error('gs_device_nccl: no backend')
517#endif
518 call device_event_record(this%event(i), this%stream(i))
519 end do
520
521 ! Sync non-blocking streams
522 do done_req = 1, size(this%recv_pe)
523 call device_stream_wait_event(strm, &
524 this%event(done_req), 0)
525 end do
526
527 end subroutine gs_device_nccl_nbwait_vec
528
529end module gs_device_nccl
void cuda_gs_unpack(real *u_d, int op, real *buf_d, int *dof_d, int offset, int n, cudaStream_t stream)
Definition gs.cu:132
void cuda_gs_pack(void *u_d, void *buf_d, void *dof_d, int offset, int n, cudaStream_t stream)
Definition gs.cu:116
void cuda_gs_pack_vec(void *u_d, void *buf_d, void *dof_d, int offset, int n, int nc, int ns, cudaStream_t stream)
Definition gs.cu:166
void cuda_gs_unpack_vec(real *u_d, int op, real *buf_d, int *dof_d, int offset, int n, int nc, int ns, cudaStream_t stream)
Definition gs.cu:183
Return the device pointer for an associated Fortran array.
Definition device.F90:113
Copy data between host and device (or device and device)
Definition device.F90:72
Definition comm.F90:1
integer, public pe_size
MPI size of communicator.
Definition comm.F90:62
integer, public pe_rank
MPI rank.
Definition comm.F90:59
Device abstraction, common interface for various accelerators.
Definition device.F90:34
subroutine, public device_event_record(event, stream)
Record a device event.
Definition device.F90:1644
integer, parameter, public host_to_device
Definition device.F90:48
subroutine, public device_free(x_d)
Deallocate memory on the device.
Definition device.F90:243
subroutine, public device_event_destroy(event)
Destroy a device event.
Definition device.F90:1623
subroutine, public device_alloc(x_d, s)
Allocate memory on the device.
Definition device.F90:212
subroutine, public device_stream_create_with_priority(stream, flags, prio)
Create a device stream/command queue with priority.
Definition device.F90:1500
subroutine, public device_stream_wait_event(stream, event, flags)
Synchronize a device stream with an event.
Definition device.F90:1544
subroutine, public device_event_create(event, flags)
Create a device event queue.
Definition device.F90:1589
integer, public strm_high_prio
High priority stream setting.
Definition device.F90:66
subroutine, public device_stream_destroy(stream)
Destroy a device stream/command queue.
Definition device.F90:1522
Defines a gather-scatter communication method.
Definition gs_comm.f90:34
integer, parameter, public gs_vec_nc
Maximum number of components handled by the fused vector (multi-component) halo exchange used by gs_o...
Definition gs_comm.f90:50
Defines NCCL based gather-scatter communication.
subroutine gs_device_nccl_nbsend_vec(this, u, n, nc, tag, deps, strm)
Fused nc-component send. u is the compact shared device buffer (component-outer, per-component stride...
subroutine gs_device_nccl_nbrecv(this, tag)
Post non-blocking receive operations.
subroutine gs_device_nccl_init(this, send_pe, recv_pe)
Initialise NCCL based communication method.
subroutine gs_device_nccl_nbsend(this, u, n, tag, deps, strm)
Post non-blocking send operations.
subroutine gs_device_nccl_nbwait(this, u, n, op, strm)
Wait for non-blocking operations.
subroutine gs_device_nccl_buf_init(this, pe_order, dof_stack, mark_dupes)
subroutine gs_device_nccl_nbrecv_vec(this, tag, nc)
No-op: send/recv and unpack happen in nbwait_vec.
subroutine gs_device_nccl_nbwait_vec(this, u, n, nc, op, strm)
Fused nc-component send/recv + unpack.
subroutine gs_device_nccl_free(this)
Deallocate MPI based communication method.
subroutine gs_device_nccl_buf_free(this)
Implements a hash table ADT.
Definition htable.f90:52
integer, parameter, public c_rp
Definition num_types.f90:15
integer, parameter, public rp
Global precision used in computations.
Definition num_types.f90:14
Implements a dynamic stack ADT.
Definition stack.f90:49
Utilities.
Definition utils.f90:35
Gather-scatter communication method.
Definition gs_comm.f90:53
Buffers for non-blocking communication and packing/unpacking.
Gather-scatter communication using NCCL The arrays are indexed per PE like send_pe and @ recv_pe.
Integer based hash table.
Definition htable.f90:102
Integer based stack.
Definition stack.f90:77
#define max(a, b)
Definition tensor.cu:40