2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 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.
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.
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.
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.
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.
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.
36 * \brief Defines routines for handling user-specified GPU IDs.
38 * \author Mark Abraham <mark.j.abraham@gmail.com>
39 * \ingroup module_taskassignment
43 #include "usergpuids.h"
52 #include "gromacs/gpu_utils/gpu_utils.h"
53 #include "gromacs/hardware/hw_info.h"
54 #include "gromacs/utility/exceptions.h"
55 #include "gromacs/utility/stringutil.h"
60 /*! \brief Parse a GPU ID specifier string into a container.
62 * \param[in] gpuIdString String like "013" or "0,1,3" typically
63 * supplied by the user.
64 * Must contain only unique decimal digits, or only decimal
65 * digits separated by comma delimiters. A terminal
66 * comma is accceptable (and required to specify a
67 * single ID that is larger than 9).
69 * \returns A vector of numeric IDs extracted from \c gpuIdString.
71 * \throws std::bad_alloc If out of memory.
72 * InvalidInputError If an invalid character is found (ie not a digit or ',').
74 static std::vector
<int>
75 parseGpuDeviceIdentifierList(const std::string
&gpuIdString
)
77 std::vector
<int> digits
;
78 auto foundCommaDelimiters
= gpuIdString
.find(',') != std::string::npos
;
79 if (!foundCommaDelimiters
)
81 for (const auto &c
: gpuIdString
)
83 if (std::isdigit(c
) == 0)
85 GMX_THROW(InvalidInputError(formatString("Invalid character in GPU ID string: \"%c\"\n", c
)));
87 // Convert each character in the token to an integer
88 digits
.push_back(c
- '0');
93 if (gpuIdString
[0] == ',')
95 GMX_THROW(InvalidInputError("Invalid use of leading comma in GPU ID string"));
97 std::istringstream
ss(gpuIdString
);
99 digits
.reserve(gpuIdString
.length());
100 token
.reserve(gpuIdString
.length());
101 while (std::getline(ss
, token
, ','))
103 // Convert the whole token to an integer
106 GMX_THROW(InvalidInputError("Invalid use of comma in GPU ID string"));
108 digits
.push_back(std::stoi(token
));
115 parseUserGpuIdString(const std::string
&gpuIdString
)
117 // An optional comma is used to separate GPU IDs assigned to the
118 // same type of task, which will be useful for any nodes that have
119 // more than ten GPUs.
121 auto digits
= parseGpuDeviceIdentifierList(gpuIdString
);
123 // Check and enforce that no duplicate IDs are allowed
124 for (size_t i
= 0; i
!= digits
.size(); ++i
)
126 for (size_t j
= i
+1; j
!= digits
.size(); ++j
)
128 if (digits
[i
] == digits
[j
])
130 GMX_THROW(InvalidInputError(formatString("The string of available GPU device IDs '%s' may not contain duplicate device IDs", gpuIdString
.c_str())));
137 std::vector
<int> makeGpuIdsToUse(const gmx_gpu_info_t
&gpuInfo
,
138 const std::string
&gpuIdsAvailableString
)
140 auto compatibleGpus
= getCompatibleGpus(gpuInfo
);
141 std::vector
<int> gpuIdsAvailable
= parseUserGpuIdString(gpuIdsAvailableString
);
143 if (gpuIdsAvailable
.empty())
145 return compatibleGpus
;
148 std::vector
<int> gpuIdsToUse
;
149 gpuIdsToUse
.reserve(gpuIdsAvailable
.size());
150 std::vector
<int> availableGpuIdsThatAreIncompatible
;
151 for (const auto &availableGpuId
: gpuIdsAvailable
)
153 bool availableGpuIsCompatible
= false;
154 for (const auto &compatibleGpuId
: compatibleGpus
)
156 if (availableGpuId
== compatibleGpuId
)
158 availableGpuIsCompatible
= true;
162 if (availableGpuIsCompatible
)
164 gpuIdsToUse
.push_back(availableGpuId
);
168 // Prepare data for an error message about all incompatible available GPU IDs.
169 availableGpuIdsThatAreIncompatible
.push_back(availableGpuId
);
172 if (!availableGpuIdsThatAreIncompatible
.empty())
174 auto message
= "You requested mdrun to use GPUs with IDs " + gpuIdsAvailableString
+
175 ", but that includes the following incompatible GPUs: " +
176 formatAndJoin(availableGpuIdsThatAreIncompatible
, ",", StringFormatter("%d")) +
177 ". Request only compatible GPUs.";
178 GMX_THROW(InvalidInputError(message
));
184 parseUserTaskAssignmentString(const std::string
&gpuIdString
)
186 // Implement any additional constraints here that need to be imposed
188 return parseGpuDeviceIdentifierList(gpuIdString
);
192 makeGpuIds(ArrayRef
<const int> compatibleGpus
,
195 std::vector
<int> gpuIdsToUse
;
197 gpuIdsToUse
.reserve(numGpuTasks
);
199 auto currentGpuId
= compatibleGpus
.begin();
200 for (size_t i
= 0; i
!= numGpuTasks
; ++i
)
202 GMX_ASSERT(!compatibleGpus
.empty(), "Must have compatible GPUs from which to build a list of GPU IDs to use");
203 gpuIdsToUse
.push_back(*currentGpuId
);
205 if (currentGpuId
== compatibleGpus
.end())
207 // Wrap around and assign tasks again.
208 currentGpuId
= compatibleGpus
.begin();
211 std::sort(gpuIdsToUse
.begin(), gpuIdsToUse
.end());
216 makeGpuIdString(const std::vector
<int> &gpuIds
,
217 int totalNumberOfTasks
)
219 auto resultGpuIds
= makeGpuIds(gpuIds
, totalNumberOfTasks
);
220 return formatAndJoin(resultGpuIds
, ",", StringFormatter("%d"));
223 void checkUserGpuIds(const gmx_gpu_info_t
&gpu_info
,
224 const std::vector
<int> &compatibleGpus
,
225 const std::vector
<int> &gpuIds
)
227 bool foundIncompatibleGpuIds
= false;
229 = "Some of the requested GPUs do not exist, behave strangely, or are not compatible:\n";
231 for (const auto &gpuId
: gpuIds
)
233 if (std::find(compatibleGpus
.begin(), compatibleGpus
.end(), gpuId
) == compatibleGpus
.end())
235 foundIncompatibleGpuIds
= true;
236 message
+= gmx::formatString(" GPU #%d: %s\n",
238 getGpuCompatibilityDescription(gpu_info
, gpuId
));
241 if (foundIncompatibleGpuIds
)
243 GMX_THROW(InconsistentInputError(message
));