Apply re-formatting to C++ in src/ tree.
[alexxy/gromacs.git] / src / gromacs / nbnxm / gpu_common.h
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2017,2018,2019,2020, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief Common functions for the different NBNXN GPU implementations.
37  *
38  * \author Szilard Pall <pall.szilard@gmail.com>
39  *
40  * \ingroup module_nbnxm
41  */
42
43 #ifndef GMX_NBNXM_GPU_COMMON_H
44 #define GMX_NBNXM_GPU_COMMON_H
45
46 #include "config.h"
47
48 #include <string>
49
50 #if GMX_GPU_CUDA
51 #    include "cuda/nbnxm_cuda_types.h"
52 #endif
53
54 #if GMX_GPU_OPENCL
55 #    include "opencl/nbnxm_ocl_types.h"
56 #endif
57
58 #include "gromacs/gpu_utils/gpu_utils.h"
59 #include "gromacs/listed_forces/gpubonded.h"
60 #include "gromacs/math/vec.h"
61 #include "gromacs/mdtypes/simulation_workload.h"
62 #include "gromacs/nbnxm/nbnxm.h"
63 #include "gromacs/pbcutil/ishift.h"
64 #include "gromacs/timing/gpu_timing.h"
65 #include "gromacs/timing/wallcycle.h"
66 #include "gromacs/utility/fatalerror.h"
67 #include "gromacs/utility/stringutil.h"
68
69 #include "gpu_common_utils.h"
70 #include "nbnxm_gpu.h"
71
72 namespace gmx
73 {
74 class GpuBonded;
75 }
76
77 namespace Nbnxm
78 {
79
80 /*! \brief Check that atom locality values are valid for the GPU module.
81  *
82  *  In the GPU module atom locality "all" is not supported, the local and
83  *  non-local ranges are treated separately.
84  *
85  *  \param[in] atomLocality atom locality specifier
86  */
87 static inline void validateGpuAtomLocality(const AtomLocality atomLocality)
88 {
89     std::string str = gmx::formatString(
90             "Invalid atom locality passed (%d); valid here is only "
91             "local (%d) or nonlocal (%d)",
92             static_cast<int>(atomLocality),
93             static_cast<int>(AtomLocality::Local),
94             static_cast<int>(AtomLocality::NonLocal));
95
96     GMX_ASSERT(atomLocality == AtomLocality::Local || atomLocality == AtomLocality::NonLocal, str.c_str());
97 }
98
99 /*! \brief Convert atom locality to interaction locality.
100  *
101  *  In the current implementation the this is straightforward conversion:
102  *  local to local, non-local to non-local.
103  *
104  *  \param[in] atomLocality Atom locality specifier
105  *  \returns                Interaction locality corresponding to the atom locality passed.
106  */
107 static inline InteractionLocality gpuAtomToInteractionLocality(const AtomLocality atomLocality)
108 {
109     validateGpuAtomLocality(atomLocality);
110
111     /* determine interaction locality from atom locality */
112     if (atomLocality == AtomLocality::Local)
113     {
114         return InteractionLocality::Local;
115     }
116     else if (atomLocality == AtomLocality::NonLocal)
117     {
118         return InteractionLocality::NonLocal;
119     }
120     else
121     {
122         gmx_incons("Wrong locality");
123     }
124 }
125
126
127 //NOLINTNEXTLINE(misc-definitions-in-headers)
128 void setupGpuShortRangeWork(NbnxmGpu* nb, const gmx::GpuBonded* gpuBonded, const gmx::InteractionLocality iLocality)
129 {
130     GMX_ASSERT(nb, "Need a valid nbnxn_gpu object");
131
132     // There is short-range work if the pair list for the provided
133     // interaction locality contains entries or if there is any
134     // bonded work (as this is not split into local/nonlocal).
135     nb->haveWork[iLocality] = ((nb->plist[iLocality]->nsci != 0)
136                                || (gpuBonded != nullptr && gpuBonded->haveInteractions()));
137 }
138
139 /*! \brief Returns true if there is GPU short-range work for the given interaction locality.
140  *
141  * Note that as, unlike nonbonded tasks, bonded tasks are not split into local/nonlocal,
142  * and therefore if there are GPU offloaded bonded interactions, this function will return
143  * true for all interaction localities.
144  *
145  * \param[inout]  nb        Pointer to the nonbonded GPU data structure
146  * \param[in]     iLocality Interaction locality identifier
147  */
148 static bool haveGpuShortRangeWork(const NbnxmGpu& nb, const gmx::InteractionLocality iLocality)
149 {
150     return nb.haveWork[iLocality];
151 }
152
153 //NOLINTNEXTLINE(misc-definitions-in-headers)
154 bool haveGpuShortRangeWork(const NbnxmGpu* nb, const gmx::AtomLocality aLocality)
155 {
156     GMX_ASSERT(nb, "Need a valid nbnxn_gpu object");
157
158     return haveGpuShortRangeWork(*nb, gpuAtomToInteractionLocality(aLocality));
159 }
160
161
162 /*! \brief Calculate atom range and return start index and length.
163  *
164  * \param[in] atomData Atom descriptor data structure
165  * \param[in] atomLocality Atom locality specifier
166  * \param[out] atomRangeBegin Starting index of the atom range in the atom data array.
167  * \param[out] atomRangeLen Atom range length in the atom data array.
168  */
169 template<typename AtomDataT>
170 static inline void getGpuAtomRange(const AtomDataT*   atomData,
171                                    const AtomLocality atomLocality,
172                                    int*               atomRangeBegin,
173                                    int*               atomRangeLen)
174 {
175     assert(atomData);
176     validateGpuAtomLocality(atomLocality);
177
178     /* calculate the atom data index range based on locality */
179     if (atomLocality == AtomLocality::Local)
180     {
181         *atomRangeBegin = 0;
182         *atomRangeLen   = atomData->natoms_local;
183     }
184     else
185     {
186         *atomRangeBegin = atomData->natoms_local;
187         *atomRangeLen   = atomData->natoms - atomData->natoms_local;
188     }
189 }
190
191
192 /*! \brief Count pruning kernel time if either kernel has been triggered
193  *
194  *  We do the accounting for either of the two pruning kernel flavors:
195  *   - 1st pass prune: ran during the current step (prior to the force kernel);
196  *   - rolling prune:  ran at the end of the previous step (prior to the current step H2D xq);
197  *
198  * Note that the resetting of cu_timers_t::didPrune and cu_timers_t::didRollingPrune should happen
199  * after calling this function.
200  *
201  * \param[in] timers   structs with GPU timer objects
202  * \param[inout] timings  GPU task timing data
203  * \param[in] iloc        interaction locality
204  */
205 template<typename GpuTimers>
206 static void countPruneKernelTime(GpuTimers*                 timers,
207                                  gmx_wallclock_gpu_nbnxn_t* timings,
208                                  const InteractionLocality  iloc)
209 {
210     gpu_timers_t::Interaction& iTimers = timers->interaction[iloc];
211
212     // We might have not done any pruning (e.g. if we skipped with empty domains).
213     if (!iTimers.didPrune && !iTimers.didRollingPrune)
214     {
215         return;
216     }
217
218     if (iTimers.didPrune)
219     {
220         timings->pruneTime.c++;
221         timings->pruneTime.t += iTimers.prune_k.getLastRangeTime();
222     }
223
224     if (iTimers.didRollingPrune)
225     {
226         timings->dynamicPruneTime.c++;
227         timings->dynamicPruneTime.t += iTimers.rollingPrune_k.getLastRangeTime();
228     }
229 }
230
231 /*! \brief Reduce data staged internally in the nbnxn module.
232  *
233  * Shift forces and electrostatic/LJ energies copied from the GPU into
234  * a module-internal staging area are immediately reduced (CPU-side buffers passed)
235  * after having waited for the transfers' completion.
236  *
237  * Note that this function should always be called after the transfers into the
238  * staging buffers has completed.
239  *
240  * \tparam     StagingData    Type of staging data
241  * \param[in]  nbst           Nonbonded staging data
242  * \param[in]  iLocality      Interaction locality specifier
243  * \param[in]  reduceEnergies True if energy reduction should be done
244  * \param[in]  reduceFshift   True if shift force reduction should be done
245  * \param[out] e_lj           Variable to accumulate LJ energy into
246  * \param[out] e_el           Variable to accumulate electrostatic energy into
247  * \param[out] fshift         Pointer to the array of shift forces to accumulate into
248  */
249 template<typename StagingData>
250 static inline void gpu_reduce_staged_outputs(const StagingData&        nbst,
251                                              const InteractionLocality iLocality,
252                                              const bool                reduceEnergies,
253                                              const bool                reduceFshift,
254                                              real*                     e_lj,
255                                              real*                     e_el,
256                                              rvec*                     fshift)
257 {
258     /* add up energies and shift forces (only once at local F wait) */
259     if (iLocality == InteractionLocality::Local)
260     {
261         if (reduceEnergies)
262         {
263             *e_lj += *nbst.e_lj;
264             *e_el += *nbst.e_el;
265         }
266
267         if (reduceFshift)
268         {
269             for (int i = 0; i < SHIFTS; i++)
270             {
271                 rvec_inc(fshift[i], nbst.fshift[i]);
272             }
273         }
274     }
275 }
276
277 /*! \brief Do the per-step timing accounting of the nonbonded tasks.
278  *
279  *  Does timing accumulation and call-count increments for the nonbonded kernels.
280  *  Note that this function should be called after the current step's nonbonded
281  *  nonbonded tasks have completed with the exception of the rolling pruning kernels
282  *  that are accounted for during the following step.
283  *
284  * NOTE: if timing with multiple GPUs (streams) becomes possible, the
285  *      counters could end up being inconsistent due to not being incremented
286  *      on some of the node when this is skipped on empty local domains!
287  *
288  * \tparam     GpuTimers         GPU timers type
289  * \tparam     GpuPairlist       Pair list type
290  * \param[out] timings           Pointer to the NB GPU timings data
291  * \param[in]  timers            Pointer to GPU timers data
292  * \param[in]  plist             Pointer to the pair list data
293  * \param[in]  atomLocality      Atom locality specifier
294  * \param[in]  stepWork          Force schedule flags
295  * \param[in]  doTiming          True if timing is enabled.
296  *
297  */
298 template<typename GpuTimers, typename GpuPairlist>
299 static inline void gpu_accumulate_timings(gmx_wallclock_gpu_nbnxn_t* timings,
300                                           GpuTimers*                 timers,
301                                           const GpuPairlist*         plist,
302                                           AtomLocality               atomLocality,
303                                           const gmx::StepWorkload&   stepWork,
304                                           bool                       doTiming)
305 {
306     /* timing data accumulation */
307     if (!doTiming)
308     {
309         return;
310     }
311
312     /* determine interaction locality from atom locality */
313     const InteractionLocality iLocality        = gpuAtomToInteractionLocality(atomLocality);
314     const bool                didEnergyKernels = stepWork.computeEnergy;
315
316     /* only increase counter once (at local F wait) */
317     if (iLocality == InteractionLocality::Local)
318     {
319         timings->nb_c++;
320         timings->ktime[plist->haveFreshList ? 1 : 0][didEnergyKernels ? 1 : 0].c += 1;
321     }
322
323     /* kernel timings */
324     timings->ktime[plist->haveFreshList ? 1 : 0][didEnergyKernels ? 1 : 0].t +=
325             timers->interaction[iLocality].nb_k.getLastRangeTime();
326
327     /* X/q H2D and F D2H timings */
328     timings->nb_h2d_t += timers->xf[atomLocality].nb_h2d.getLastRangeTime();
329     timings->nb_d2h_t += timers->xf[atomLocality].nb_d2h.getLastRangeTime();
330
331     /* Count the pruning kernel times for both cases:1st pass (at search step)
332        and rolling pruning (if called at the previous step).
333        We do the accounting here as this is the only sync point where we
334        know (without checking or additional sync-ing) that prune tasks in
335        in the current stream have completed (having just blocking-waited
336        for the force D2H). */
337     countPruneKernelTime(timers, timings, iLocality);
338
339     /* only count atdat at pair-search steps (add only once, at local F wait) */
340     if (stepWork.doNeighborSearch && atomLocality == AtomLocality::Local)
341     {
342         /* atdat transfer timing */
343         timings->pl_h2d_c++;
344         timings->pl_h2d_t += timers->atdat.getLastRangeTime();
345     }
346
347     /* only count pair-list H2D when actually performed */
348     if (timers->interaction[iLocality].didPairlistH2D)
349     {
350         timings->pl_h2d_t += timers->interaction[iLocality].pl_h2d.getLastRangeTime();
351
352         /* Clear the timing flag for the next step */
353         timers->interaction[iLocality].didPairlistH2D = false;
354     }
355 }
356
357 /*! \brief Attempts to complete nonbonded GPU task.
358  *
359  * See documentation in nbnxm_gpu.h for details.
360  *
361  * \todo Move into shared source file, perhaps including
362  * cuda_runtime.h if needed for any remaining CUDA-specific
363  * objects.
364  */
365 //NOLINTNEXTLINE(misc-definitions-in-headers)
366 bool gpu_try_finish_task(NbnxmGpu*                nb,
367                          const gmx::StepWorkload& stepWork,
368                          const AtomLocality       aloc,
369                          real*                    e_lj,
370                          real*                    e_el,
371                          gmx::ArrayRef<gmx::RVec> shiftForces,
372                          GpuTaskCompletion        completionKind,
373                          gmx_wallcycle*           wcycle)
374 {
375     GMX_ASSERT(nb, "Need a valid nbnxn_gpu object");
376
377     /* determine interaction locality from atom locality */
378     const InteractionLocality iLocality = gpuAtomToInteractionLocality(aloc);
379
380
381     // Transfers are launched and therefore need to be waited on if:
382     // - buffer ops is not offloaded
383     // - energies or virials are needed (on the local stream)
384     //
385     // (Note that useGpuFBufferOps and computeVirial are mutually exclusive
386     // in current code as virial steps do CPU reduction.)
387     const bool haveResultToWaitFor =
388             (!stepWork.useGpuFBufferOps
389              || (aloc == AtomLocality::Local && (stepWork.computeEnergy || stepWork.computeVirial)));
390
391     //  We skip when during the non-local phase there was actually no work to do.
392     //  This is consistent with nbnxn_gpu_launch_kernel but it also considers possible
393     //  bonded GPU work.
394     if ((iLocality == InteractionLocality::Local) || haveGpuShortRangeWork(*nb, iLocality))
395     {
396         // Query the state of the GPU stream and return early if we're not done
397         if (completionKind == GpuTaskCompletion::Check)
398         {
399             // To get the wcycle call count right, when in GpuTaskCompletion::Check mode,
400             // we start without counting and only when the task finished we issue a
401             // start/stop to increment.
402             // GpuTaskCompletion::Wait mode the timing is expected to be done in the caller.
403             wallcycle_start_nocount(wcycle, ewcWAIT_GPU_NB_L);
404
405             if (!haveStreamTasksCompleted(*nb->deviceStreams[iLocality]))
406             {
407                 wallcycle_stop(wcycle, ewcWAIT_GPU_NB_L);
408
409                 // Early return to skip the steps below that we have to do only
410                 // after the NB task completed
411                 return false;
412             }
413
414             wallcycle_increment_event_count(wcycle, ewcWAIT_GPU_NB_L);
415         }
416         else if (haveResultToWaitFor)
417         {
418             nb->deviceStreams[iLocality]->synchronize();
419         }
420
421         // TODO: this needs to be moved later because conditional wait could brake timing
422         // with a future OpenCL implementation, but with CUDA timing is anyway disabled
423         // in all cases where we skip the wait.
424         gpu_accumulate_timings(nb->timings, nb->timers, nb->plist[iLocality], aloc, stepWork, nb->bDoTime);
425
426         if (stepWork.computeEnergy || stepWork.computeVirial)
427         {
428             gpu_reduce_staged_outputs(nb->nbst,
429                                       iLocality,
430                                       stepWork.computeEnergy,
431                                       stepWork.computeVirial,
432                                       e_lj,
433                                       e_el,
434                                       as_rvec_array(shiftForces.data()));
435         }
436     }
437
438     /* Always reset both pruning flags (doesn't hurt doing it even when timing is off). */
439     nb->timers->interaction[iLocality].didPrune =
440             nb->timers->interaction[iLocality].didRollingPrune = false;
441
442     /* Turn off initial list pruning (doesn't hurt if this is not pair-search step). */
443     nb->plist[iLocality]->haveFreshList = false;
444
445     return true;
446 }
447
448 /*! \brief
449  * Wait for the asynchronously launched nonbonded tasks and data
450  * transfers to finish.
451  *
452  * Also does timing accounting and reduction of the internal staging buffers.
453  * As this is called at the end of the step, it also resets the pair list and
454  * pruning flags.
455  *
456  * \param[in] nb The nonbonded data GPU structure
457  * \param[in]  stepWork     Force schedule flags
458  * \param[in] aloc Atom locality identifier
459  * \param[out] e_lj Pointer to the LJ energy output to accumulate into
460  * \param[out] e_el Pointer to the electrostatics energy output to accumulate into
461  * \param[out] shiftForces Shift forces buffer to accumulate into
462  * \param[out] wcycle Pointer to wallcycle data structure
463  * \return            The number of cycles the gpu wait took
464  */
465 //NOLINTNEXTLINE(misc-definitions-in-headers) TODO: move into source file
466 float gpu_wait_finish_task(NbnxmGpu*                nb,
467                            const gmx::StepWorkload& stepWork,
468                            AtomLocality             aloc,
469                            real*                    e_lj,
470                            real*                    e_el,
471                            gmx::ArrayRef<gmx::RVec> shiftForces,
472                            gmx_wallcycle*           wcycle)
473 {
474     auto cycleCounter = (gpuAtomToInteractionLocality(aloc) == InteractionLocality::Local)
475                                 ? ewcWAIT_GPU_NB_L
476                                 : ewcWAIT_GPU_NB_NL;
477
478     wallcycle_start(wcycle, cycleCounter);
479     gpu_try_finish_task(nb, stepWork, aloc, e_lj, e_el, shiftForces, GpuTaskCompletion::Wait, wcycle);
480     float waitTime = wallcycle_stop(wcycle, cycleCounter);
481
482     return waitTime;
483 }
484
485 } // namespace Nbnxm
486
487 #endif