2 * This file is part of the GROMACS molecular simulation package.
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.
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/hardware/device_management.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> parseGpuDeviceIdentifierList(const std::string
& gpuIdString
)
76 std::vector
<int> digits
;
77 auto foundCommaDelimiters
= gpuIdString
.find(',') != std::string::npos
;
78 if (!foundCommaDelimiters
)
80 for (const auto& c
: gpuIdString
)
82 if (std::isdigit(c
) == 0)
84 GMX_THROW(InvalidInputError(
85 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
));
114 std::vector
<int> parseUserGpuIdString(const std::string
& gpuIdString
)
116 // An optional comma is used to separate GPU IDs assigned to the
117 // same type of task, which will be useful for any nodes that have
118 // more than ten GPUs.
120 auto digits
= parseGpuDeviceIdentifierList(gpuIdString
);
122 // Check and enforce that no duplicate IDs are allowed
123 for (size_t i
= 0; i
!= digits
.size(); ++i
)
125 for (size_t j
= i
+ 1; j
!= digits
.size(); ++j
)
127 if (digits
[i
] == digits
[j
])
130 InvalidInputError(formatString("The string of available GPU device IDs "
131 "'%s' may not contain duplicate device IDs",
132 gpuIdString
.c_str())));
139 std::vector
<int> makeGpuIdsToUse(const std::vector
<std::unique_ptr
<DeviceInformation
>>& deviceInfoList
,
140 const std::string
& gpuIdsAvailableString
)
142 std::vector
<int> gpuIdsAvailable
= parseUserGpuIdString(gpuIdsAvailableString
);
144 if (gpuIdsAvailable
.empty())
146 // The user didn't restrict the choice, so we use all compatible GPUs
147 return getCompatibleDeviceIds(deviceInfoList
);
150 std::vector
<int> gpuIdsToUse
;
151 gpuIdsToUse
.reserve(gpuIdsAvailable
.size());
152 std::vector
<int> availableGpuIdsThatAreIncompatible
;
153 for (const int& availableGpuId
: gpuIdsAvailable
)
155 if (deviceIdIsCompatible(deviceInfoList
, availableGpuId
))
157 gpuIdsToUse
.push_back(availableGpuId
);
161 // Prepare data for an error message about all incompatible available GPU IDs.
162 availableGpuIdsThatAreIncompatible
.push_back(availableGpuId
);
165 if (!availableGpuIdsThatAreIncompatible
.empty())
167 auto message
= "You requested mdrun to use GPUs with IDs " + gpuIdsAvailableString
168 + ", but that includes the following incompatible GPUs: "
169 + formatAndJoin(availableGpuIdsThatAreIncompatible
, ",", StringFormatter("%d"))
170 + ". Request only compatible GPUs.";
171 GMX_THROW(InvalidInputError(message
));
176 std::vector
<int> parseUserTaskAssignmentString(const std::string
& gpuIdString
)
178 // Implement any additional constraints here that need to be imposed
180 return parseGpuDeviceIdentifierList(gpuIdString
);
183 std::vector
<int> makeGpuIds(ArrayRef
<const int> compatibleGpus
, size_t numGpuTasks
)
185 std::vector
<int> gpuIdsToUse
;
187 gpuIdsToUse
.reserve(numGpuTasks
);
189 auto currentGpuId
= compatibleGpus
.begin();
190 for (size_t i
= 0; i
!= numGpuTasks
; ++i
)
192 GMX_ASSERT(!compatibleGpus
.empty(),
193 "Must have compatible GPUs from which to build a list of GPU IDs to use");
194 gpuIdsToUse
.push_back(*currentGpuId
);
196 if (currentGpuId
== compatibleGpus
.end())
198 // Wrap around and assign tasks again.
199 currentGpuId
= compatibleGpus
.begin();
202 std::sort(gpuIdsToUse
.begin(), gpuIdsToUse
.end());
206 std::string
makeGpuIdString(const std::vector
<int>& gpuIds
, int totalNumberOfTasks
)
208 auto resultGpuIds
= makeGpuIds(gpuIds
, totalNumberOfTasks
);
209 return formatAndJoin(resultGpuIds
, ",", StringFormatter("%d"));
212 void checkUserGpuIds(const std::vector
<std::unique_ptr
<DeviceInformation
>>& deviceInfoList
,
213 const std::vector
<int>& compatibleGpus
,
214 const std::vector
<int>& gpuIds
)
216 bool foundIncompatibleGpuIds
= false;
217 std::string message
=
218 "Some of the requested GPUs do not exist, behave strangely, or are not compatible:\n";
220 for (const auto& gpuId
: gpuIds
)
222 if (std::find(compatibleGpus
.begin(), compatibleGpus
.end(), gpuId
) == compatibleGpus
.end())
224 foundIncompatibleGpuIds
= true;
225 message
+= gmx::formatString(" GPU #%d: %s\n", gpuId
,
226 getDeviceCompatibilityDescription(deviceInfoList
, gpuId
).c_str());
229 if (foundIncompatibleGpuIds
)
231 GMX_THROW(InconsistentInputError(message
));