Apply clang-format to source tree
[alexxy/gromacs.git] / src / gromacs / taskassignment / resourcedivision.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2015,2016,2017,2018,2019, 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 Defines utility functionality for dividing resources and
37  * checking for consistency and usefulness.
38  *
39  * \author Mark Abraham <mark.j.abraham@gmail.com>
40  * \ingroup module_taskassignment
41  */
42
43 #include "gmxpre.h"
44
45 #include "resourcedivision.h"
46
47 #include "config.h"
48
49 #include <cstdlib>
50 #include <cstring>
51
52 #include <algorithm>
53
54 #include "gromacs/ewald/pme.h"
55 #include "gromacs/hardware/cpuinfo.h"
56 #include "gromacs/hardware/detecthardware.h"
57 #include "gromacs/hardware/hardwaretopology.h"
58 #include "gromacs/hardware/hw_info.h"
59 #include "gromacs/math/functions.h"
60 #include "gromacs/mdlib/gmx_omp_nthreads.h"
61 #include "gromacs/mdrunutility/multisim.h"
62 #include "gromacs/mdtypes/commrec.h"
63 #include "gromacs/mdtypes/inputrec.h"
64 #include "gromacs/mdtypes/md_enums.h"
65 #include "gromacs/topology/mtop_util.h"
66 #include "gromacs/topology/topology.h"
67 #include "gromacs/utility/baseversion.h"
68 #include "gromacs/utility/fatalerror.h"
69 #include "gromacs/utility/gmxassert.h"
70 #include "gromacs/utility/logger.h"
71 #include "gromacs/utility/physicalnodecommunicator.h"
72 #include "gromacs/utility/stringutil.h"
73
74
75 /* DISCLAIMER: All the atom count and thread numbers below are heuristic.
76  * The real switching points will depend on the system simulation,
77  * the algorithms used and the hardware it's running on, as well as if there
78  * are other jobs running on the same machine. We try to take into account
79  * factors that have a large influence, such as recent Intel CPUs being
80  * much better at wide multi-threading. The remaining factors should
81  * (hopefully) have a small influence, such that the performance just before
82  * and after a switch point doesn't change too much.
83  */
84
85 /*! \brief The minimum number of atoms per thread-MPI thread when GPUs
86  * are present. With fewer atoms than this, the number of thread-MPI
87  * ranks will get lowered.
88  */
89 static constexpr int min_atoms_per_mpi_thread = 90;
90 /*! \brief The minimum number of atoms per GPU with thread-MPI
91  * active. With fewer atoms than this, the number of thread-MPI ranks
92  * will get lowered.
93  */
94 static constexpr int min_atoms_per_gpu = 900;
95
96 /**@{*/
97 /*! \brief Constants for implementing default divisions of threads */
98
99 /* TODO choose nthreads_omp based on hardware topology
100    when we have a hardware topology detection library */
101 /* First we consider the case of no MPI (1 MPI rank).
102  * In general, when running up to 8 threads, OpenMP should be faster.
103  * Note: on AMD Bulldozer we should avoid running OpenMP over two dies.
104  * On Intel>=Nehalem running OpenMP on a single CPU is always faster,
105  * even on two CPUs it's usually faster (but with many OpenMP threads
106  * it could be faster not to use HT, currently we always use HT).
107  * On Nehalem/Westmere we want to avoid running 16 threads over
108  * two CPUs with HT, so we need a limit<16; thus we use 12.
109  * A reasonable limit for Intel Sandy and Ivy bridge,
110  * not knowing the topology, is 16 threads.
111  * Below we check for Intel and AVX, which for now includes
112  * Sandy/Ivy Bridge, Has/Broadwell. By checking for AVX instead of
113  * model numbers we ensure also future Intel CPUs are covered.
114  */
115 constexpr int nthreads_omp_faster_default   = 8;
116 constexpr int nthreads_omp_faster_Nehalem   = 12;
117 constexpr int nthreads_omp_faster_Intel_AVX = 16;
118 constexpr int nthreads_omp_faster_AMD_Ryzen = 16;
119 /* For CPU only runs the fastest options are usually MPI or OpenMP only.
120  * With one GPU, using MPI only is almost never optimal, so we need to
121  * compare running pure OpenMP with combined MPI+OpenMP. This means higher
122  * OpenMP threads counts can still be ok. Multiplying the numbers above
123  * by a factor of 2 seems to be a good estimate.
124  */
125 constexpr int nthreads_omp_faster_gpu_fac = 2;
126
127 /* This is the case with MPI (2 or more MPI PP ranks).
128  * By default we will terminate with a fatal error when more than 8
129  * OpenMP thread are (indirectly) requested, since using less threads
130  * nearly always results in better performance.
131  * With thread-mpi and multiple GPUs or one GPU and too many threads
132  * we first try 6 OpenMP threads and then less until the number of MPI ranks
133  * is divisible by the number of GPUs.
134  */
135 constexpr int nthreads_omp_mpi_ok_max     = 8;
136 constexpr int nthreads_omp_mpi_ok_min_cpu = 1;
137 constexpr int nthreads_omp_mpi_ok_min_gpu = 2;
138 constexpr int nthreads_omp_mpi_target_max = 6;
139
140 /**@}*/
141
142 /*! \brief Returns the maximum OpenMP thread count for which using a single MPI rank
143  * should be faster than using multiple ranks with the same total thread count.
144  */
145 static int nthreads_omp_faster(const gmx::CpuInfo& cpuInfo, gmx_bool bUseGPU)
146 {
147     int nth;
148
149     if (cpuInfo.vendor() == gmx::CpuInfo::Vendor::Intel && cpuInfo.feature(gmx::CpuInfo::Feature::X86_Avx))
150     {
151         nth = nthreads_omp_faster_Intel_AVX;
152     }
153     else if (gmx::cpuIsX86Nehalem(cpuInfo))
154     {
155         // Intel Nehalem
156         nth = nthreads_omp_faster_Nehalem;
157     }
158     else if ((cpuInfo.vendor() == gmx::CpuInfo::Vendor::Amd && cpuInfo.family() >= 23)
159              || cpuInfo.vendor() == gmx::CpuInfo::Vendor::Hygon)
160     {
161         // AMD Ryzen || Hygon Dhyana
162         nth = nthreads_omp_faster_AMD_Ryzen;
163     }
164     else
165     {
166         nth = nthreads_omp_faster_default;
167     }
168
169     if (bUseGPU)
170     {
171         nth *= nthreads_omp_faster_gpu_fac;
172     }
173
174     nth = std::min(nth, GMX_OPENMP_MAX_THREADS);
175
176     return nth;
177 }
178
179 /*! \brief Returns that maximum OpenMP thread count that passes the efficiency check */
180 gmx_unused static int nthreads_omp_efficient_max(int gmx_unused nrank, const gmx::CpuInfo& cpuInfo, gmx_bool bUseGPU)
181 {
182     if (GMX_OPENMP && GMX_MPI && (nrank > 1))
183     {
184         return nthreads_omp_mpi_ok_max;
185     }
186     else
187     {
188         return nthreads_omp_faster(cpuInfo, bUseGPU);
189     }
190 }
191
192 /*! \brief Return the number of thread-MPI ranks to use.
193  * This is chosen such that we can always obey our own efficiency checks.
194  */
195 gmx_unused static int get_tmpi_omp_thread_division(const gmx_hw_info_t* hwinfo,
196                                                    const gmx_hw_opt_t&  hw_opt,
197                                                    int                  nthreads_tot,
198                                                    int                  ngpu)
199 {
200     int                 nrank;
201     const gmx::CpuInfo& cpuInfo = *hwinfo->cpuInfo;
202
203     GMX_RELEASE_ASSERT(nthreads_tot > 0, "There must be at least one thread per rank");
204
205     /* There are no separate PME nodes here, as we ensured in
206      * check_and_update_hw_opt that nthreads_tmpi>0 with PME nodes
207      * and a conditional ensures we would not have ended up here.
208      * Note that separate PME nodes might be switched on later.
209      */
210     if (ngpu > 0)
211     {
212         if (hw_opt.nthreads_omp > 0)
213         {
214             /* In this case it is unclear if we should use 1 rank per GPU
215              * or more or less, so we require also setting the number of ranks.
216              */
217             gmx_fatal(FARGS,
218                       "When using GPUs, setting the number of OpenMP threads without specifying "
219                       "the number "
220                       "of ranks can lead to conflicting demands. Please specify the number of "
221                       "thread-MPI ranks "
222                       "as well (option -ntmpi).");
223         }
224
225         nrank = ngpu;
226
227         /* When the user sets nthreads_omp, we can end up oversubscribing CPU cores
228          * if we simply start as many ranks as GPUs. To avoid this, we start as few
229          * tMPI ranks as necessary to avoid oversubscription and instead leave GPUs idle.
230          * If the user does not set the number of OpenMP threads, nthreads_omp==0 and
231          * this code has no effect.
232          */
233         GMX_RELEASE_ASSERT(hw_opt.nthreads_omp >= 0,
234                            "nthreads_omp is negative, but previous checks should "
235                            "have prevented this");
236         while (nrank * hw_opt.nthreads_omp > hwinfo->nthreads_hw_avail && nrank > 1)
237         {
238             nrank--;
239         }
240
241         if (nthreads_tot < nrank)
242         {
243             /* #thread < #gpu is very unlikely, but if so: waste gpu(s) */
244             nrank = nthreads_tot;
245         }
246         else if (nthreads_tot > nthreads_omp_faster(cpuInfo, ngpu > 0)
247                  || (ngpu > 1 && nthreads_tot / ngpu > nthreads_omp_mpi_target_max))
248         {
249             /* The high OpenMP thread count will likely result in sub-optimal
250              * performance. Increase the rank count to reduce the thread count
251              * per rank. This will lead to GPU sharing by MPI ranks/threads.
252              */
253             int nshare;
254
255             /* Increase the rank count as long as have we more than 6 OpenMP
256              * threads per rank or the number of hardware threads is not
257              * divisible by the rank count. Don't go below 2 OpenMP threads.
258              */
259             nshare = 1;
260             do
261             {
262                 nshare++;
263                 nrank = ngpu * nshare;
264             } while (nthreads_tot / nrank > nthreads_omp_mpi_target_max
265                      || (nthreads_tot / (ngpu * (nshare + 1)) >= nthreads_omp_mpi_ok_min_gpu
266                          && nthreads_tot % nrank != 0));
267         }
268     }
269     else if (hw_opt.nthreads_omp > 0)
270     {
271         /* Here we could oversubscribe, when we do, we issue a warning later */
272         nrank = std::max(1, nthreads_tot / hw_opt.nthreads_omp);
273     }
274     else
275     {
276         if (nthreads_tot <= nthreads_omp_faster(cpuInfo, ngpu > 0))
277         {
278             /* Use pure OpenMP parallelization */
279             nrank = 1;
280         }
281         else
282         {
283             /* Don't use OpenMP parallelization */
284             nrank = nthreads_tot;
285         }
286     }
287
288     return nrank;
289 }
290
291 //! Return whether hyper threading is enabled.
292 static bool gmxSmtIsEnabled(const gmx::HardwareTopology& hwTop)
293 {
294     return (hwTop.supportLevel() >= gmx::HardwareTopology::SupportLevel::Basic
295             && hwTop.machine().sockets[0].cores[0].hwThreads.size() > 1);
296 }
297
298 namespace
299 {
300
301 //! Handles checks for algorithms that must use a single rank.
302 class SingleRankChecker
303 {
304 public:
305     SingleRankChecker() : value_(false) {}
306     /*! \brief Call this function for each possible condition
307         under which a single rank is required, along with a string
308         describing the constraint when it is applied. */
309     void applyConstraint(bool condition, const char* description)
310     {
311         if (condition)
312         {
313             value_ = true;
314             reasons_.push_back(gmx::formatString("%s only supports a single rank.", description));
315         }
316     }
317     //! After applying any conditions, is a single rank required?
318     bool mustUseOneRank() const { return value_; }
319     /*! \brief Return a formatted string to use when writing a
320         message when a single rank is required, (or empty if no
321         constraint exists.) */
322     std::string getMessage() const
323     {
324         return formatAndJoin(reasons_, "\n", gmx::IdentityFormatter());
325     }
326
327 private:
328     bool                     value_;
329     std::vector<std::string> reasons_;
330 };
331
332 } // namespace
333
334 /* Get the number of MPI ranks to use for thread-MPI based on how many
335  * were requested, which algorithms we're using,
336  * and how many particles there are.
337  * At the point we have already called check_and_update_hw_opt.
338  * Thus all options should be internally consistent and consistent
339  * with the hardware, except that ntmpi could be larger than #GPU.
340  */
341 int get_nthreads_mpi(const gmx_hw_info_t*    hwinfo,
342                      gmx_hw_opt_t*           hw_opt,
343                      const std::vector<int>& gpuIdsToUse,
344                      bool                    nonbondedOnGpu,
345                      bool                    pmeOnGpu,
346                      const t_inputrec*       inputrec,
347                      const gmx_mtop_t*       mtop,
348                      const gmx::MDLogger&    mdlog,
349                      bool                    doMembed)
350 {
351     int nthreads_hw, nthreads_tot_max, nrank, ngpu;
352     int min_atoms_per_mpi_rank;
353
354     const gmx::CpuInfo&          cpuInfo = *hwinfo->cpuInfo;
355     const gmx::HardwareTopology& hwTop   = *hwinfo->hardwareTopology;
356
357     if (pmeOnGpu)
358     {
359         GMX_RELEASE_ASSERT((EEL_PME(inputrec->coulombtype) || EVDW_PME(inputrec->vdwtype))
360                                    && pme_gpu_supports_build(nullptr)
361                                    && pme_gpu_supports_hardware(*hwinfo, nullptr)
362                                    && pme_gpu_supports_input(*inputrec, *mtop, nullptr),
363                            "PME can't be on GPUs unless we are using PME");
364
365         // PME on GPUs supports a single PME rank with PP running on the same or few other ranks.
366         // For now, let's treat separate PME GPU rank as opt-in.
367         if (hw_opt->nthreads_tmpi < 1)
368         {
369             return 1;
370         }
371     }
372
373     {
374         /* Check if an algorithm does not support parallel simulation.  */
375         // TODO This might work better if e.g. implemented algorithms
376         // had to define a function that returns such requirements,
377         // and a description string.
378         SingleRankChecker checker;
379         checker.applyConstraint(inputrec->eI == eiLBFGS, "L-BFGS minimization");
380         checker.applyConstraint(inputrec->coulombtype == eelEWALD, "Plain Ewald electrostatics");
381         checker.applyConstraint(doMembed, "Membrane embedding");
382         bool useOrientationRestraints = (gmx_mtop_ftype_count(mtop, F_ORIRES) > 0);
383         checker.applyConstraint(useOrientationRestraints, "Orientation restraints");
384         if (checker.mustUseOneRank())
385         {
386             std::string message = checker.getMessage();
387             if (hw_opt->nthreads_tmpi > 1)
388             {
389                 gmx_fatal(FARGS,
390                           "%s However, you asked for more than 1 thread-MPI rank, so mdrun cannot "
391                           "continue. "
392                           "Choose a single rank, or a different algorithm.",
393                           message.c_str());
394             }
395             GMX_LOG(mdlog.warning)
396                     .asParagraph()
397                     .appendTextFormatted("%s Choosing to use only a single thread-MPI rank.",
398                                          message.c_str());
399             return 1;
400         }
401     }
402
403     if (hw_opt->nthreads_tmpi > 0)
404     {
405         /* Trivial, return the user's choice right away */
406         return hw_opt->nthreads_tmpi;
407     }
408
409     // Now implement automatic selection of number of thread-MPI ranks
410     nthreads_hw = hwinfo->nthreads_hw_avail;
411
412     if (nthreads_hw <= 0)
413     {
414         /* This should normally not happen, but if it does, we handle it */
415         gmx_fatal(FARGS,
416                   "The number of available hardware threads can not be detected, please specify "
417                   "the number of "
418                   "MPI ranks and the number of OpenMP threads (if supported) manually with options "
419                   "-ntmpi and -ntomp, respectively");
420     }
421
422     /* How many total (#tMPI*#OpenMP) threads can we start? */
423     if (hw_opt->nthreads_tot > 0)
424     {
425         nthreads_tot_max = hw_opt->nthreads_tot;
426     }
427     else
428     {
429         nthreads_tot_max = nthreads_hw;
430     }
431
432     /* nonbondedOnGpu might be false e.g. because this simulation
433      * is a rerun with energy groups. */
434     ngpu = (nonbondedOnGpu ? gmx::ssize(gpuIdsToUse) : 0);
435
436     nrank = get_tmpi_omp_thread_division(hwinfo, *hw_opt, nthreads_tot_max, ngpu);
437
438     if (inputrec->eI == eiNM || EI_TPI(inputrec->eI))
439     {
440         /* Dims/steps are divided over the nodes iso splitting the atoms.
441          * With NM we can't have more ranks than #atoms*#dim. With TPI it's
442          * unlikely we have fewer atoms than ranks, and if so, communication
443          * would become a bottleneck, so we set the limit to 1 atom/rank.
444          */
445         min_atoms_per_mpi_rank = 1;
446     }
447     else
448     {
449         if (ngpu >= 1)
450         {
451             min_atoms_per_mpi_rank = min_atoms_per_gpu;
452         }
453         else
454         {
455             min_atoms_per_mpi_rank = min_atoms_per_mpi_thread;
456         }
457     }
458
459     if (mtop->natoms / nrank < min_atoms_per_mpi_rank)
460     {
461         int nrank_new;
462
463         /* the rank number was chosen automatically, but there are too few
464            atoms per rank, so we need to reduce the rank count */
465         nrank_new = std::max(1, mtop->natoms / min_atoms_per_mpi_rank);
466
467         /* Avoid partial use of Hyper-Threading */
468         if (gmxSmtIsEnabled(hwTop) && nrank_new > nthreads_hw / 2 && nrank_new < nthreads_hw)
469         {
470             nrank_new = nthreads_hw / 2;
471         }
472
473         /* If the user specified the total thread count, ensure this is
474          * divisible by the number of ranks.
475          * It is quite likely that we have too many total threads compared
476          * to the size of the system, but if the user asked for this many
477          * threads we should respect that.
478          */
479         while (hw_opt->nthreads_tot > 0 && hw_opt->nthreads_tot % nrank_new != 0)
480         {
481             nrank_new--;
482         }
483
484         /* Avoid large prime numbers in the rank count */
485         if (nrank_new >= 6)
486         {
487             /* Use only 6,8,10 with additional factors of 2 */
488             int fac;
489
490             fac = 2;
491             while (3 * fac * 2 <= nrank_new)
492             {
493                 fac *= 2;
494             }
495
496             nrank_new = (nrank_new / fac) * fac;
497         }
498         else
499         {
500             /* Avoid 5, since small system won't fit 5 domains along
501              * a dimension. This might lead to waisting some cores, but this
502              * will have a small impact in this regime of very small systems.
503              */
504             if (nrank_new == 5)
505             {
506                 nrank_new = 4;
507             }
508         }
509
510         nrank = nrank_new;
511
512         /* We reduced the number of tMPI ranks, which means we might violate
513          * our own efficiency checks if we simply use all hardware threads.
514          */
515         if (GMX_OPENMP && hw_opt->nthreads_omp <= 0 && hw_opt->nthreads_tot <= 0)
516         {
517             /* The user set neither the total nor the OpenMP thread count,
518              * we should use all hardware threads, unless we will violate
519              * our own efficiency limitation on the thread count.
520              */
521             int nt_omp_max;
522
523             nt_omp_max = nthreads_omp_efficient_max(nrank, cpuInfo, ngpu >= 1);
524
525             if (nrank * nt_omp_max < hwinfo->nthreads_hw_avail)
526             {
527                 /* Limit the number of OpenMP threads to start */
528                 hw_opt->nthreads_omp = nt_omp_max;
529             }
530         }
531
532         fprintf(stderr, "\n");
533         fprintf(stderr, "NOTE: Parallelization is limited by the small number of atoms,\n");
534         fprintf(stderr, "      only starting %d thread-MPI ranks.\n", nrank);
535         fprintf(stderr,
536                 "      You can use the -nt and/or -ntmpi option to optimize the number of "
537                 "threads.\n\n");
538     }
539
540     return nrank;
541 }
542
543
544 void check_resource_division_efficiency(const gmx_hw_info_t* hwinfo,
545                                         bool                 willUsePhysicalGpu,
546                                         gmx_bool             bNtOmpOptionSet,
547                                         t_commrec*           cr,
548                                         const gmx::MDLogger& mdlog)
549 {
550 #if GMX_OPENMP && GMX_MPI
551     GMX_UNUSED_VALUE(hwinfo);
552
553     int         nth_omp_min, nth_omp_max;
554     char        buf[1000];
555     const char* mpi_option = GMX_THREAD_MPI ? " (option -ntmpi)" : "";
556
557     /* This function should be called after thread-MPI (when configured) and
558      * OpenMP have been initialized. Check that here.
559      */
560     if (GMX_THREAD_MPI)
561     {
562         GMX_RELEASE_ASSERT(nthreads_omp_faster_default >= nthreads_omp_mpi_ok_max,
563                            "Inconsistent OpenMP thread count default values");
564     }
565     GMX_RELEASE_ASSERT(gmx_omp_nthreads_get(emntDefault) >= 1,
566                        "Must have at least one OpenMP thread");
567
568     nth_omp_min = gmx_omp_nthreads_get(emntDefault);
569     nth_omp_max = gmx_omp_nthreads_get(emntDefault);
570
571     bool anyRankIsUsingGpus = willUsePhysicalGpu;
572     /* Thread-MPI seems to have a bug with reduce on 1 node, so use a cond. */
573     if (cr->nnodes > 1)
574     {
575         int count[3], count_max[3];
576
577         count[0] = -nth_omp_min;
578         count[1] = nth_omp_max;
579         count[2] = int(willUsePhysicalGpu);
580
581         MPI_Allreduce(count, count_max, 3, MPI_INT, MPI_MAX, cr->mpi_comm_mysim);
582
583         /* In case of an inhomogeneous run setup we use the maximum counts */
584         nth_omp_min        = -count_max[0];
585         nth_omp_max        = count_max[1];
586         anyRankIsUsingGpus = count_max[2] > 0;
587     }
588
589     int nthreads_omp_mpi_ok_min;
590
591     if (!anyRankIsUsingGpus)
592     {
593         nthreads_omp_mpi_ok_min = nthreads_omp_mpi_ok_min_cpu;
594     }
595     else
596     {
597         /* With GPUs we set the minimum number of OpenMP threads to 2 to catch
598          * cases where the user specifies #ranks == #cores.
599          */
600         nthreads_omp_mpi_ok_min = nthreads_omp_mpi_ok_min_gpu;
601     }
602
603     if (DOMAINDECOMP(cr))
604     {
605         if (nth_omp_max < nthreads_omp_mpi_ok_min || nth_omp_max > nthreads_omp_mpi_ok_max)
606         {
607             /* Note that we print target_max here, not ok_max */
608             sprintf(buf,
609                     "Your choice of number of MPI ranks and amount of resources results in using "
610                     "%d OpenMP "
611                     "threads per rank, which is most likely inefficient. The optimum is usually "
612                     "between %d and"
613                     " %d threads per rank.",
614                     nth_omp_max, nthreads_omp_mpi_ok_min, nthreads_omp_mpi_target_max);
615
616             if (bNtOmpOptionSet)
617             {
618                 GMX_LOG(mdlog.warning).asParagraph().appendTextFormatted("NOTE: %s", buf);
619             }
620             else
621             {
622                 /* This fatal error, and the one below, is nasty, but it's
623                  * probably the only way to ensure that all users don't waste
624                  * a lot of resources, since many users don't read logs/stderr.
625                  */
626                 gmx_fatal(FARGS,
627                           "%s If you want to run with this setup, specify the -ntomp option. But "
628                           "we suggest to "
629                           "change the number of MPI ranks%s.",
630                           buf, mpi_option);
631             }
632         }
633     }
634 #else  // !GMX_OPENMP || ! GMX_MPI
635     GMX_UNUSED_VALUE(bNtOmpOptionSet);
636     GMX_UNUSED_VALUE(willUsePhysicalGpu);
637     GMX_UNUSED_VALUE(cr);
638     GMX_UNUSED_VALUE(nthreads_omp_mpi_ok_max);
639     GMX_UNUSED_VALUE(nthreads_omp_mpi_ok_min_cpu);
640     /* Check if we have more than 1 physical core, if detected,
641      * or more than 1 hardware thread if physical cores were not detected.
642      */
643     if (!GMX_OPENMP && !GMX_MPI && hwinfo->hardwareTopology->numberOfCores() > 1)
644     {
645         GMX_LOG(mdlog.warning)
646                 .asParagraph()
647                 .appendText(
648                         "NOTE: GROMACS was compiled without OpenMP and (thread-)MPI support, can "
649                         "only use a single CPU core");
650     }
651 #endif // end GMX_OPENMP && GMX_MPI
652 }
653
654
655 //! Dump a \c hw_opt to \c fp.
656 static void print_hw_opt(FILE* fp, const gmx_hw_opt_t* hw_opt)
657 {
658     fprintf(fp, "hw_opt: nt %d ntmpi %d ntomp %d ntomp_pme %d gpu_id '%s' gputasks '%s'\n",
659             hw_opt->nthreads_tot, hw_opt->nthreads_tmpi, hw_opt->nthreads_omp, hw_opt->nthreads_omp_pme,
660             hw_opt->gpuIdsAvailable.c_str(), hw_opt->userGpuTaskAssignment.c_str());
661 }
662
663 void checkAndUpdateHardwareOptions(const gmx::MDLogger& mdlog,
664                                    gmx_hw_opt_t*        hw_opt,
665                                    const bool           isSimulationMasterRank,
666                                    const int            nPmeRanks,
667                                    const t_inputrec*    inputrec)
668 {
669     /* Currently hw_opt only contains default settings or settings supplied
670      * by the user on the command line.
671      */
672     if (hw_opt->nthreads_omp < 0)
673     {
674         gmx_fatal(FARGS,
675                   "The number of OpenMP threads supplied on the command line is %d, which is "
676                   "negative "
677                   "and not allowed",
678                   hw_opt->nthreads_omp);
679     }
680
681     /* Check for OpenMP settings stored in environment variables, which can
682      * potentially be different on different MPI ranks.
683      */
684     gmx_omp_nthreads_read_env(mdlog, &hw_opt->nthreads_omp);
685
686     /* Check restrictions on the user supplied options before modifying them.
687      * TODO: Put the user values in a const struct and preserve them.
688      */
689     if (!GMX_THREAD_MPI)
690     {
691
692         if (hw_opt->nthreads_tot > 0)
693         {
694             gmx_fatal(FARGS,
695                       "Setting the total number of threads is only supported with thread-MPI and "
696                       "GROMACS was "
697                       "compiled without thread-MPI");
698         }
699         if (hw_opt->nthreads_tmpi > 0)
700         {
701             gmx_fatal(FARGS,
702                       "Setting the number of thread-MPI ranks is only supported with thread-MPI "
703                       "and GROMACS was "
704                       "compiled without thread-MPI");
705         }
706     }
707
708     /* With thread-MPI we need to handle TPI and #OpenMP-threads=auto early,
709      * so we can parallelize using MPI only. The general check is done later.
710      */
711     if (GMX_THREAD_MPI && isSimulationMasterRank)
712     {
713         GMX_RELEASE_ASSERT(inputrec, "Expect a valid inputrec");
714         if (EI_TPI(inputrec->eI) && hw_opt->nthreads_omp == 0)
715         {
716             hw_opt->nthreads_omp = 1;
717         }
718     }
719     /* With thread-MPI the master thread sets hw_opt->totNumThreadsIsAuto.
720      * The other threads receive a partially processed hw_opt from the master
721      * thread and should not set hw_opt->totNumThreadsIsAuto again.
722      */
723     if (!GMX_THREAD_MPI || isSimulationMasterRank)
724     {
725         /* Check if mdrun is free to choose the total number of threads */
726         hw_opt->totNumThreadsIsAuto = (hw_opt->nthreads_omp == 0 && hw_opt->nthreads_omp_pme == 0
727                                        && hw_opt->nthreads_tot == 0);
728     }
729
730     if (GMX_OPENMP)
731     {
732         /* Check restrictions on PME thread related options set by the user */
733
734         if (hw_opt->nthreads_omp_pme > 0 && hw_opt->nthreads_omp <= 0)
735         {
736             gmx_fatal(FARGS, "You need to specify -ntomp in addition to -ntomp_pme");
737         }
738
739         if (hw_opt->nthreads_omp_pme >= 1 && hw_opt->nthreads_omp_pme != hw_opt->nthreads_omp
740             && nPmeRanks <= 0)
741         {
742             /* This can result in a fatal error on many MPI ranks,
743              * but since the thread count can differ per rank,
744              * we can't easily avoid this.
745              */
746             gmx_fatal(FARGS,
747                       "You need to explicitly specify the number of PME ranks (-npme) when using "
748                       "different numbers of OpenMP threads for PP and PME ranks");
749         }
750     }
751     else
752     {
753         /* GROMACS was configured without OpenMP support */
754
755         if (hw_opt->nthreads_omp > 1 || hw_opt->nthreads_omp_pme > 1)
756         {
757             gmx_fatal(FARGS,
758                       "More than 1 OpenMP thread requested, but GROMACS was compiled without "
759                       "OpenMP support");
760         }
761         hw_opt->nthreads_omp     = 1;
762         hw_opt->nthreads_omp_pme = 1;
763     }
764
765     if (hw_opt->nthreads_tot > 0 && hw_opt->nthreads_omp_pme <= 0)
766     {
767         /* We have the same number of OpenMP threads for PP and PME ranks,
768          * thus we can perform several consistency checks.
769          */
770         if (hw_opt->nthreads_tmpi > 0 && hw_opt->nthreads_omp > 0
771             && hw_opt->nthreads_tot != hw_opt->nthreads_tmpi * hw_opt->nthreads_omp)
772         {
773             gmx_fatal(FARGS,
774                       "The total number of threads requested (%d) does not match the thread-MPI "
775                       "ranks (%d) "
776                       "times the OpenMP threads (%d) requested",
777                       hw_opt->nthreads_tot, hw_opt->nthreads_tmpi, hw_opt->nthreads_omp);
778         }
779
780         if (hw_opt->nthreads_tmpi > 0 && hw_opt->nthreads_tot % hw_opt->nthreads_tmpi != 0)
781         {
782             gmx_fatal(FARGS,
783                       "The total number of threads requested (%d) is not divisible by the number "
784                       "of thread-MPI "
785                       "ranks requested (%d)",
786                       hw_opt->nthreads_tot, hw_opt->nthreads_tmpi);
787         }
788
789         if (hw_opt->nthreads_omp > 0 && hw_opt->nthreads_tot % hw_opt->nthreads_omp != 0)
790         {
791             gmx_fatal(FARGS,
792                       "The total number of threads requested (%d) is not divisible by the number "
793                       "of OpenMP "
794                       "threads requested (%d)",
795                       hw_opt->nthreads_tot, hw_opt->nthreads_omp);
796         }
797     }
798
799     if (hw_opt->nthreads_tot > 0)
800     {
801         if (hw_opt->nthreads_omp > hw_opt->nthreads_tot)
802         {
803             gmx_fatal(FARGS,
804                       "You requested %d OpenMP threads with %d total threads. Choose a total "
805                       "number of threads "
806                       "that is a multiple of the number of OpenMP threads.",
807                       hw_opt->nthreads_omp, hw_opt->nthreads_tot);
808         }
809
810         if (hw_opt->nthreads_tmpi > hw_opt->nthreads_tot)
811         {
812             gmx_fatal(FARGS,
813                       "You requested %d thread-MPI ranks with %d total threads. Choose a total "
814                       "number of "
815                       "threads that is a multiple of the number of thread-MPI ranks.",
816                       hw_opt->nthreads_tmpi, hw_opt->nthreads_tot);
817         }
818     }
819
820     if (GMX_THREAD_MPI && nPmeRanks > 0 && hw_opt->nthreads_tmpi <= 0)
821     {
822         gmx_fatal(FARGS,
823                   "You need to explicitly specify the number of MPI threads (-ntmpi) when using "
824                   "separate PME ranks");
825     }
826
827     if (debug)
828     {
829         print_hw_opt(debug, hw_opt);
830     }
831
832     /* Asserting this simplifies the hardware resource division later
833      * on. */
834     GMX_RELEASE_ASSERT(
835             !(hw_opt->nthreads_omp_pme >= 1 && hw_opt->nthreads_omp <= 0),
836             "PME thread count should only be set when the normal thread count is also set");
837 }
838
839 void checkAndUpdateRequestedNumOpenmpThreads(gmx_hw_opt_t*         hw_opt,
840                                              const gmx_hw_info_t&  hwinfo,
841                                              const t_commrec*      cr,
842                                              const gmx_multisim_t* ms,
843                                              int                   numRanksOnThisNode,
844                                              PmeRunMode            pmeRunMode,
845                                              const gmx_mtop_t&     mtop,
846                                              const t_inputrec&     inputrec)
847 {
848     if (EI_TPI(inputrec.eI))
849     {
850         if (hw_opt->nthreads_omp > 1)
851         {
852             gmx_fatal(FARGS,
853                       "You requested OpenMP parallelization, which is not supported with TPI.");
854         }
855         hw_opt->nthreads_omp = 1;
856     }
857
858     if (GMX_THREAD_MPI)
859     {
860
861         GMX_RELEASE_ASSERT(hw_opt->nthreads_tmpi >= 1, "Must have at least one thread-MPI rank");
862
863         /* If the user set the total number of threads on the command line
864          * and did not specify the number of OpenMP threads, set the latter here.
865          */
866         if (hw_opt->nthreads_tot > 0 && hw_opt->nthreads_omp <= 0)
867         {
868             hw_opt->nthreads_omp = hw_opt->nthreads_tot / hw_opt->nthreads_tmpi;
869
870             if (!GMX_OPENMP && hw_opt->nthreads_omp > 1)
871             {
872                 gmx_fatal(FARGS,
873                           "You (indirectly) asked for OpenMP threads by setting -nt > -ntmpi, but "
874                           "GROMACS was "
875                           "compiled without OpenMP support");
876             }
877         }
878     }
879     /* With both non-bonded and PME on GPU, the work left on the CPU is often
880      * (much) slower with SMT than without SMT. This is mostly the case with
881      * few atoms per core. Thus, if the number of threads is set to auto,
882      * we turn off SMT in that case. Note that PME on GPU implies that also
883      * the non-bonded are computed on the GPU.
884      * We only need to do this when the number of hardware theads is larger
885      * than the number of cores. Note that a queuing system could limit
886      * the number of hardware threads available, but we are not trying to be
887      * too smart here in that case.
888      */
889     /* The thread reduction and synchronization costs go up roughy quadratically
890      * with the threads count, so we apply a threshold quadratic in #cores.
891      * Also more cores per GPU usually means the CPU gets faster than the GPU.
892      * The number 1000 atoms per core^2 is a reasonable threshold
893      * for Intel x86 and AMD Threadripper.
894      */
895     constexpr int c_numAtomsPerCoreSquaredSmtThreshold = 1000;
896
897     /* Prepare conditions for deciding if we should disable SMT.
898      * We currently only limit SMT for simulations using a single rank.
899      * TODO: Consider limiting also for multi-rank simulations.
900      */
901     bool canChooseNumOpenmpThreads = (GMX_OPENMP && hw_opt->nthreads_omp <= 0);
902     bool haveSmtSupport =
903             (hwinfo.hardwareTopology->supportLevel() >= gmx::HardwareTopology::SupportLevel::Basic
904              && hwinfo.hardwareTopology->machine().logicalProcessorCount
905                         > hwinfo.hardwareTopology->numberOfCores());
906     bool simRunsSingleRankNBAndPmeOnGpu = (cr->nnodes == 1 && pmeRunMode == PmeRunMode::GPU);
907
908     if (canChooseNumOpenmpThreads && haveSmtSupport && simRunsSingleRankNBAndPmeOnGpu)
909     {
910         /* Note that the queing system might have limited us from using
911          * all detected ncore_tot physical cores. We are currently not
912          * checking for that here.
913          */
914         int numRanksTot     = cr->nnodes * (isMultiSim(ms) ? ms->nsim : 1);
915         int numAtomsPerRank = mtop.natoms / cr->nnodes;
916         int numCoresPerRank = hwinfo.ncore_tot / numRanksTot;
917         if (numAtomsPerRank < c_numAtomsPerCoreSquaredSmtThreshold * gmx::square(numCoresPerRank))
918         {
919             /* Choose one OpenMP thread per physical core */
920             hw_opt->nthreads_omp =
921                     std::max(1, hwinfo.hardwareTopology->numberOfCores() / numRanksOnThisNode);
922         }
923     }
924
925     GMX_RELEASE_ASSERT(GMX_OPENMP || hw_opt->nthreads_omp == 1,
926                        "Without OpenMP support, only one thread per rank can be used");
927
928     /* We are done with updating nthreads_omp, we can set nthreads_omp_pme */
929     if (hw_opt->nthreads_omp_pme <= 0 && hw_opt->nthreads_omp > 0)
930     {
931         hw_opt->nthreads_omp_pme = hw_opt->nthreads_omp;
932     }
933
934     if (debug)
935     {
936         print_hw_opt(debug, hw_opt);
937     }
938 }
939
940 namespace gmx
941 {
942
943 void checkHardwareOversubscription(int                             numThreadsOnThisRank,
944                                    int                             rank,
945                                    const HardwareTopology&         hwTop,
946                                    const PhysicalNodeCommunicator& comm,
947                                    const MDLogger&                 mdlog)
948 {
949     if (hwTop.supportLevel() < HardwareTopology::SupportLevel::LogicalProcessorCount)
950     {
951         /* There is nothing we can check */
952         return;
953     }
954
955     int numRanksOnThisNode   = comm.size_;
956     int numThreadsOnThisNode = numThreadsOnThisRank;
957     /* Avoid MPI calls with uninitialized thread-MPI communicators */
958     if (comm.size_ > 1)
959     {
960 #if GMX_MPI
961         /* Count the threads within this physical node */
962         MPI_Allreduce(&numThreadsOnThisRank, &numThreadsOnThisNode, 1, MPI_INT, MPI_SUM, comm.comm_);
963 #endif
964     }
965
966     if (numThreadsOnThisNode > hwTop.machine().logicalProcessorCount)
967     {
968         std::string mesg = "WARNING: ";
969         if (GMX_LIB_MPI)
970         {
971             mesg += formatString("On rank %d: o", rank);
972         }
973         else
974         {
975             mesg += "O";
976         }
977         mesg += formatString("versubscribing the available %d logical CPU cores",
978                              hwTop.machine().logicalProcessorCount);
979         if (GMX_LIB_MPI)
980         {
981             mesg += " per node";
982         }
983         mesg += formatString(" with %d ", numThreadsOnThisNode);
984         if (numRanksOnThisNode == numThreadsOnThisNode)
985         {
986             if (GMX_THREAD_MPI)
987             {
988                 mesg += "thread-MPI threads.";
989             }
990             else
991             {
992                 mesg += "MPI processes.";
993             }
994         }
995         else
996         {
997             mesg += "threads.";
998         }
999         mesg += "\n         This will cause considerable performance loss.";
1000         /* Note that only the master rank logs to stderr and only ranks
1001          * with an open log file write to log.
1002          * TODO: When we have a proper parallel logging framework,
1003          *       the framework should add the rank and node numbers.
1004          */
1005         GMX_LOG(mdlog.warning).asParagraph().appendTextFormatted("%s", mesg.c_str());
1006     }
1007 }
1008
1009 } // namespace gmx