Merge branch 'origin/release-2021' into merge-2021-into-master
[alexxy/gromacs.git] / src / gromacs / gpu_utils / devicebuffer_sycl.h
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 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 #ifndef GMX_GPU_UTILS_DEVICEBUFFER_SYCL_H
36 #define GMX_GPU_UTILS_DEVICEBUFFER_SYCL_H
37
38 /*! \libinternal \file
39  *  \brief Implements the DeviceBuffer type and routines for SYCL.
40  *  Should only be included directly by the main DeviceBuffer file devicebuffer.h.
41  *  TODO: the intent is for DeviceBuffer to become a class.
42  *
43  *  \author Artem Zhmurov <zhmurov@gmail.com>
44  *  \author Erik Lindahl <erik.lindahl@gmail.com>
45  *  \author Andrey Alekseenko <al42and@gmail.com>
46  *
47  *  \inlibraryapi
48  */
49
50 #include "gromacs/gpu_utils/device_context.h"
51 #include "gromacs/gpu_utils/device_stream.h"
52 #include "gromacs/gpu_utils/devicebuffer_datatype.h"
53 #include "gromacs/gpu_utils/gmxsycl.h"
54 #include "gromacs/gpu_utils/gpu_utils.h" //only for GpuApiCallBehavior
55 #include "gromacs/gpu_utils/gputraits_sycl.h"
56 #include "gromacs/utility/gmxassert.h"
57 #include "gromacs/utility/stringutil.h"
58
59 #ifndef DOXYGEN
60 template<typename T>
61 class DeviceBuffer<T>::ClSyclBufferWrapper : public cl::sycl::buffer<T, 1>
62 {
63     using cl::sycl::buffer<T, 1>::buffer; // Get all the constructors
64 };
65
66 template<typename T>
67 using ClSyclBufferWrapper = typename DeviceBuffer<T>::ClSyclBufferWrapper;
68
69 //! Constructor.
70 template<typename T>
71 DeviceBuffer<T>::DeviceBuffer() : buffer_(nullptr)
72 {
73 }
74
75 //! Destructor.
76 template<typename T>
77 DeviceBuffer<T>::~DeviceBuffer() = default;
78
79 //! Copy constructor (references the same underlying SYCL buffer).
80 template<typename T>
81 DeviceBuffer<T>::DeviceBuffer(DeviceBuffer<T> const& src) :
82     buffer_(new ClSyclBufferWrapper(*src.buffer_))
83 {
84 }
85
86 //! Move constructor.
87 template<typename T>
88 DeviceBuffer<T>::DeviceBuffer(DeviceBuffer<T>&& src) noexcept = default;
89
90 //! Copy assignment (references the same underlying SYCL buffer).
91 template<typename T>
92 DeviceBuffer<T>& DeviceBuffer<T>::operator=(DeviceBuffer<T> const& src)
93 {
94     buffer_.reset(new ClSyclBufferWrapper(*src.buffer_));
95     return *this;
96 }
97
98 //! Move assignment.
99 template<typename T>
100 DeviceBuffer<T>& DeviceBuffer<T>::operator=(DeviceBuffer<T>&& src) noexcept = default;
101
102 /*! \brief Dummy assignment operator to allow compilation of some cross-platform code.
103  *
104  * A hacky way to make SYCL implementation of DeviceBuffer compatible with details of CUDA and
105  * OpenCL implementations.
106  *
107  * \todo Should be removed after DeviceBuffer refactoring.
108  *
109  * \tparam T Type of buffer content.
110  * \param nullPtr \c std::nullptr. Not possible to assign any other pointers.
111  */
112 template<typename T>
113 DeviceBuffer<T>& DeviceBuffer<T>::operator=(std::nullptr_t nullPtr)
114 {
115     buffer_.reset(nullPtr);
116     return *this;
117 }
118
119
120 namespace gmx::internal
121 {
122 //! Shorthand alias to create a placeholder SYCL accessor with chosen data type and access mode.
123 template<class T, enum cl::sycl::access::mode mode>
124 using PlaceholderAccessor =
125         cl::sycl::accessor<T, 1, mode, cl::sycl::access::target::global_buffer, cl::sycl::access::placeholder::true_t>;
126 } // namespace gmx::internal
127
128 /** \brief
129  * Thin wrapper around placeholder accessor that allows implicit construction from \c DeviceBuffer.
130  *
131  * "Placeholder accessor" is an indicator of the intent to create an accessor for certain buffer
132  * of a certain type, that is not yet bound to a specific command group handler (device). Such
133  * accessors can be created outside SYCL kernels, which is helpful if we want to pass them as
134  * function arguments.
135  *
136  * \tparam T Type of buffer content.
137  * \tparam mode Access mode.
138  */
139 template<class T, enum cl::sycl::access::mode mode>
140 class DeviceAccessor : public gmx::internal::PlaceholderAccessor<T, mode>
141 {
142 public:
143     // Inherit all the constructors
144     using gmx::internal::PlaceholderAccessor<T, mode>::PlaceholderAccessor;
145     //! Construct Accessor from DeviceBuffer (must be initialized)
146     DeviceAccessor(DeviceBuffer<T>& buffer) :
147         gmx::internal::PlaceholderAccessor<T, mode>(getSyclBuffer(buffer))
148     {
149     }
150
151 private:
152     //! Helper function to get sycl:buffer object from DeviceBuffer wrapper, with a sanity check.
153     static inline cl::sycl::buffer<T, 1>& getSyclBuffer(DeviceBuffer<T>& buffer)
154     {
155         GMX_ASSERT(bool(buffer), "Trying to construct accessor from an uninitialized buffer");
156         return *buffer.buffer_;
157     }
158 };
159
160 namespace gmx::internal
161 {
162 //! A "blackhole" class to be used when we want to ignore an argument to a function.
163 struct EmptyClassThatIgnoresConstructorArguments
164 {
165     template<class... Args>
166     [[maybe_unused]] EmptyClassThatIgnoresConstructorArguments(Args&&... /*args*/)
167     {
168     }
169 };
170 } // namespace gmx::internal
171
172 /** \brief
173  * Helper class to be used as function argument. Will either correspond to a device accessor, or an empty class.
174  *
175  * Example usage:
176  * \code
177     template <bool doFoo>
178     void getBarKernel(handler& cgh, OptionalAccessor<float, mode::read, doFoo> a_fooPrms)
179     {
180         if constexpr (doFoo)
181             cgh.require(a_fooPrms);
182         // Can only use a_fooPrms if doFoo == true
183     }
184
185     template <bool doFoo>
186     void callBar(DeviceBuffer<float> b_fooPrms)
187     {
188         // If doFoo is false, b_fooPrms will be ignored (can be not initialized).
189         // Otherwise, an accessor will be built (b_fooPrms must be a valid buffer).
190         auto kernel = getBarKernel<doFoo>(b_fooPrms);
191         // If the accessor in not enabled, anything can be passed as its ctor argument.
192         auto kernel2 = getBarKernel<false>(nullptr_t);
193     }
194  * \endcode
195  *
196  * \tparam T Data type of the underlying buffer
197  * \tparam mode Access mode of the accessor
198  * \tparam enabled Compile-time flag indicating whether we want to actually create an accessor.
199  */
200 template<class T, enum cl::sycl::access::mode mode, bool enabled>
201 using OptionalAccessor =
202         std::conditional_t<enabled, DeviceAccessor<T, mode>, gmx::internal::EmptyClassThatIgnoresConstructorArguments>;
203
204 #endif // #ifndef DOXYGEN
205
206 /*! \brief Check the validity of the device buffer.
207  *
208  * Checks if the buffer is valid and if its allocation is big enough.
209  *
210  * \param[in] buffer        Device buffer to be checked.
211  * \param[in] requiredSize  Number of elements that the buffer will have to accommodate.
212  *
213  * \returns Whether the device buffer exists and has enough capacity.
214  */
215 template<typename T>
216 static gmx_unused bool checkDeviceBuffer(const DeviceBuffer<T>& buffer, int requiredSize)
217 {
218     return buffer.buffer_ && (static_cast<int>(buffer.buffer_->get_count()) >= requiredSize);
219 }
220
221 /*! \libinternal \brief
222  * Allocates a device-side buffer.
223  * It is currently a caller's responsibility to call it only on not-yet allocated buffers.
224  *
225  * \tparam        ValueType            Raw value type of the \p buffer.
226  * \param[in,out] buffer               Pointer to the device-side buffer.
227  * \param[in]     numValues            Number of values to accommodate.
228  * \param[in]     deviceContext        The buffer's device context-to-be.
229  */
230 template<typename ValueType>
231 void allocateDeviceBuffer(DeviceBuffer<ValueType>* buffer, size_t numValues, const DeviceContext& deviceContext)
232 {
233     /* SYCL does not require binding buffer to a specific context or device. The ::context_bound
234      * property only enforces the use of only given context, and possibly offers some optimizations */
235     const cl::sycl::property_list bufferProperties{ cl::sycl::property::buffer::context_bound(
236             deviceContext.context()) };
237     buffer->buffer_.reset(
238             new ClSyclBufferWrapper<ValueType>(cl::sycl::range<1>(numValues), bufferProperties));
239 }
240
241 /*! \brief
242  * Frees a device-side buffer.
243  * This does not reset separately stored size/capacity integers,
244  * as this is planned to be a destructor of DeviceBuffer as a proper class,
245  * and no calls on \p buffer should be made afterwards.
246  *
247  * \param[in] buffer  Pointer to the buffer to free.
248  */
249 template<typename ValueType>
250 void freeDeviceBuffer(DeviceBuffer<ValueType>* buffer)
251 {
252     buffer->buffer_.reset(nullptr);
253 }
254
255 /*! \brief
256  * Performs the host-to-device data copy, synchronous or asynchronously on request.
257  *
258  * Unlike in CUDA and OpenCL, synchronous call does not guarantee that all previously
259  * submitted operations are complete, only the ones that are required for \p buffer consistency.
260  *
261  * \tparam        ValueType            Raw value type of the \p buffer.
262  * \param[in,out] buffer               Pointer to the device-side buffer.
263  * \param[in]     hostBuffer           Pointer to the raw host-side memory, also typed \p ValueType.
264  * \param[in]     startingOffset       Offset (in values) at the device-side buffer to copy into.
265  * \param[in]     numValues            Number of values to copy.
266  * \param[in]     deviceStream         GPU stream to perform asynchronous copy in.
267  * \param[in]     transferKind         Copy type: synchronous or asynchronous.
268  * \param[out]    timingEvent          A pointer to the H2D copy timing event to be filled in.
269  *                                     Ignored in SYCL.
270  */
271 template<typename ValueType>
272 void copyToDeviceBuffer(DeviceBuffer<ValueType>* buffer,
273                         const ValueType*         hostBuffer,
274                         size_t                   startingOffset,
275                         size_t                   numValues,
276                         const DeviceStream&      deviceStream,
277                         GpuApiCallBehavior       transferKind,
278                         CommandEvent* gmx_unused timingEvent)
279 {
280     if (numValues == 0)
281     {
282         return; // such calls are actually made with empty domains
283     }
284     GMX_ASSERT(buffer, "needs a buffer pointer");
285     GMX_ASSERT(hostBuffer, "needs a host buffer pointer");
286
287     GMX_ASSERT(checkDeviceBuffer(*buffer, startingOffset + numValues),
288                "buffer too small or not initialized");
289
290     cl::sycl::buffer<ValueType>& syclBuffer = *buffer->buffer_;
291
292     cl::sycl::event ev = deviceStream.stream().submit([&](cl::sycl::handler& cgh) {
293         /* Here and elsewhere in this file, accessor constructor is user instead of a more common
294          * buffer::get_access, since the compiler (icpx 2021.1-beta09) occasionally gets confused
295          * by all the overloads */
296         auto d_bufferAccessor = cl::sycl::accessor<ValueType, 1, cl::sycl::access::mode::discard_write>{
297             syclBuffer, cgh, cl::sycl::range(numValues), cl::sycl::id(startingOffset)
298         };
299         cgh.copy(hostBuffer, d_bufferAccessor);
300     });
301     if (transferKind == GpuApiCallBehavior::Sync)
302     {
303         ev.wait_and_throw();
304     }
305 }
306
307 /*! \brief
308  * Performs the device-to-host data copy, synchronous or asynchronously on request.
309  *
310  * Unlike in CUDA and OpenCL, synchronous call does not guarantee that all previously
311  * submitted operations are complete, only the ones that are required for \p buffer consistency.
312  *
313  * \tparam        ValueType            Raw value type of the \p buffer.
314  * \param[in,out] hostBuffer           Pointer to the raw host-side memory, also typed \p ValueType
315  * \param[in]     buffer               Pointer to the device-side buffer.
316  * \param[in]     startingOffset       Offset (in values) at the device-side buffer to copy from.
317  * \param[in]     numValues            Number of values to copy.
318  * \param[in]     deviceStream         GPU stream to perform asynchronous copy in.
319  * \param[in]     transferKind         Copy type: synchronous or asynchronous.
320  * \param[out]    timingEvent          A pointer to the H2D copy timing event to be filled in.
321  *                                     Ignored in SYCL.
322  */
323 template<typename ValueType>
324 void copyFromDeviceBuffer(ValueType*               hostBuffer,
325                           DeviceBuffer<ValueType>* buffer,
326                           size_t                   startingOffset,
327                           size_t                   numValues,
328                           const DeviceStream&      deviceStream,
329                           GpuApiCallBehavior       transferKind,
330                           CommandEvent* gmx_unused timingEvent)
331 {
332     if (numValues == 0)
333     {
334         return; // such calls are actually made with empty domains
335     }
336     GMX_ASSERT(buffer, "needs a buffer pointer");
337     GMX_ASSERT(hostBuffer, "needs a host buffer pointer");
338
339     GMX_ASSERT(checkDeviceBuffer(*buffer, startingOffset + numValues),
340                "buffer too small or not initialized");
341
342     cl::sycl::buffer<ValueType>& syclBuffer = *buffer->buffer_;
343
344     cl::sycl::event ev = deviceStream.stream().submit([&](cl::sycl::handler& cgh) {
345         const auto d_bufferAccessor = cl::sycl::accessor<ValueType, 1, cl::sycl::access::mode::read>{
346             syclBuffer, cgh, cl::sycl::range(numValues), cl::sycl::id(startingOffset)
347         };
348         cgh.copy(d_bufferAccessor, hostBuffer);
349     });
350     if (transferKind == GpuApiCallBehavior::Sync)
351     {
352         ev.wait_and_throw();
353     }
354 }
355
356 /*! \brief
357  * Clears the device buffer asynchronously.
358  *
359  * \tparam        ValueType       Raw value type of the \p buffer.
360  * \param[in,out] buffer          Pointer to the device-side buffer.
361  * \param[in]     startingOffset  Offset (in values) at the device-side buffer to start clearing at.
362  * \param[in]     numValues       Number of values to clear.
363  * \param[in]     deviceStream    GPU stream.
364  */
365 template<typename ValueType>
366 void clearDeviceBufferAsync(DeviceBuffer<ValueType>* buffer,
367                             size_t                   startingOffset,
368                             size_t                   numValues,
369                             const DeviceStream&      deviceStream)
370 {
371     if (numValues == 0)
372     {
373         return;
374     }
375     GMX_ASSERT(buffer, "needs a buffer pointer");
376
377     GMX_ASSERT(checkDeviceBuffer(*buffer, startingOffset + numValues),
378                "buffer too small or not initialized");
379
380     const ValueType              pattern{};
381     cl::sycl::buffer<ValueType>& syclBuffer = *(buffer->buffer_);
382
383     cl::sycl::event ev = deviceStream.stream().submit([&](cl::sycl::handler& cgh) {
384         auto d_bufferAccessor = cl::sycl::accessor<ValueType, 1, cl::sycl::access::mode::discard_write>{
385             syclBuffer, cgh, cl::sycl::range(numValues), cl::sycl::id(startingOffset)
386         };
387         cgh.fill(d_bufferAccessor, pattern);
388     });
389 }
390
391 /*! \brief Create a texture object for an array of type ValueType.
392  *
393  * Creates the device buffer and copies read-only data for an array of type ValueType.
394  * Like OpenCL, does not really do anything with textures, simply creates a buffer
395  * and initializes it.
396  *
397  * \tparam      ValueType      Raw data type.
398  *
399  * \param[out]  deviceBuffer   Device buffer to store data in.
400  * \param[in]   hostBuffer     Host buffer to get date from.
401  * \param[in]   numValues      Number of elements in the buffer.
402  * \param[in]   deviceContext  GPU device context.
403  */
404 template<typename ValueType>
405 void initParamLookupTable(DeviceBuffer<ValueType>* deviceBuffer,
406                           DeviceTexture* /* deviceTexture */,
407                           const ValueType*     hostBuffer,
408                           int                  numValues,
409                           const DeviceContext& deviceContext)
410 {
411     GMX_ASSERT(hostBuffer, "Host buffer should be specified.");
412     GMX_ASSERT(deviceBuffer, "Device buffer should be specified.");
413
414     /* Constructing buffer with cl::sycl::buffer(T* data, size_t size) will take ownership
415      * of this memory region making it unusable, which might lead to side-effects.
416      * On the other hand, cl::sycl::buffer(InputIterator<T> begin, InputIterator<T> end) will
417      * initialize the buffer without affecting ownership of the memory, although
418      * it will consume extra memory on host. */
419     const cl::sycl::property_list bufferProperties{ cl::sycl::property::buffer::context_bound(
420             deviceContext.context()) };
421     deviceBuffer->buffer_.reset(new ClSyclBufferWrapper<ValueType>(
422             hostBuffer, hostBuffer + numValues, bufferProperties));
423 }
424
425 /*! \brief Release the OpenCL device buffer.
426  *
427  * \tparam        ValueType     Raw data type.
428  *
429  * \param[in,out] deviceBuffer  Device buffer to store data in.
430  */
431 template<typename ValueType>
432 void destroyParamLookupTable(DeviceBuffer<ValueType>* deviceBuffer, DeviceTexture& /* deviceTexture */)
433 {
434     deviceBuffer->buffer_.reset(nullptr);
435 }
436
437 #endif // GMX_GPU_UTILS_DEVICEBUFFER_SYCL_H