GpuBuffer.hpp
Go to the documentation of this file.
1/*
2 Copyright 2024 SINTEF AS
3
4 This file is part of the Open Porous Media project (OPM).
5
6 OPM is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 OPM is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with OPM. If not, see <http://www.gnu.org/licenses/>.
18*/
19#ifndef OPM_GPUBUFFER_HEADER_HPP
20#define OPM_GPUBUFFER_HEADER_HPP
21
27
28#include <opm/common/ErrorMacros.hpp>
29
30#include <cuda_runtime.h>
31#include <dune/common/fvector.hh>
32#include <dune/istl/bvector.hh>
33#include <fmt/format.h>
34
35#include <algorithm>
36#include <cstddef>
37#include <memory>
38#include <stdexcept>
39#include <type_traits>
40#include <vector>
41
42
43namespace Opm::gpuistl
44{
45
65template <typename T>
66class GpuBuffer
67{
68public:
69 using field_type = T;
70 using size_type = size_t;
71 using value_type = T;
72
76 GpuBuffer() = default;
77
86 GpuBuffer(const GpuBuffer<T>& other)
87 : GpuBuffer(other.m_numberOfElements)
88 {
89 assertSameSize(other);
90 if (m_numberOfElements == 0) {
91 return;
92 }
93 detail::gpuMemcpyDeviceToDevice(m_dataOnDevice, other.m_dataOnDevice, m_numberOfElements);
94 }
95
104 GpuBuffer(GpuBuffer<T>&& other) noexcept
105 : m_dataOnDevice(other.m_dataOnDevice)
106 , m_numberOfElements(other.m_numberOfElements)
107 {
108 other.m_dataOnDevice = nullptr;
109 other.m_numberOfElements = 0;
110 }
111
120 GpuBuffer<T>& operator=(GpuBuffer<T>&& other) noexcept
121 {
122 if (this != &other) {
123 OPM_GPU_WARN_IF_ERROR(cudaFree(m_dataOnDevice));
124 m_dataOnDevice = other.m_dataOnDevice;
125 m_numberOfElements = other.m_numberOfElements;
126 other.m_dataOnDevice = nullptr;
127 other.m_numberOfElements = 0;
128 }
129 return *this;
130 }
131
141 explicit GpuBuffer(const std::vector<T>& data)
142 : GpuBuffer(data.size())
143 {
144 copyFromHost(data);
145 }
146
152 explicit GpuBuffer(const size_t numberOfElements)
153 : m_numberOfElements(numberOfElements)
154 {
155 OPM_GPU_SAFE_CALL(cudaMalloc(&m_dataOnDevice, sizeof(T) * m_numberOfElements));
156 }
157
158
168 GpuBuffer(const T* dataOnHost, const size_t numberOfElements)
169 : GpuBuffer(numberOfElements)
170 {
171 detail::gpuMemcpyHostToDevice(m_dataOnDevice, dataOnHost, m_numberOfElements);
172 }
173
174
178 virtual ~GpuBuffer()
179 {
180 OPM_GPU_WARN_IF_ERROR(cudaFree(m_dataOnDevice));
181 }
182
186 T* data()
187 {
188 return m_dataOnDevice;
189 }
190
194 const T* data() const
195 {
196 return m_dataOnDevice;
197 }
198
206 template <int BlockDimension>
207 void copyFromHost(const Dune::BlockVector<Dune::FieldVector<T, BlockDimension>>& bvector)
208 {
209 if (m_numberOfElements != bvector.dim()) {
210 OPM_THROW(std::runtime_error,
211 fmt::format("Given incompatible vector size. GpuBuffer has size {},\n however, the BlockVector "
212 "has dim() = {} (N() = {}, and size() = {}).",
213 m_numberOfElements,
214 bvector.dim(),
215 bvector.N(),
216 bvector.size()));
217 }
218 const auto dataPointer = static_cast<const T*>(&(bvector[0][0]));
219 copyFromHost(dataPointer, m_numberOfElements);
220 }
221
229 template <int BlockDimension>
230 void copyToHost(Dune::BlockVector<Dune::FieldVector<T, BlockDimension>>& bvector) const
231 {
232 if (m_numberOfElements != bvector.dim()) {
233 OPM_THROW(std::runtime_error,
234 fmt::format("Given incompatible vector size. GpuBuffer has size {},\n however, the BlockVector "
235 "has dim() = {} (N() = {}, and size() = {}).",
236 m_numberOfElements,
237 bvector.dim(),
238 bvector.N(),
239 bvector.size()));
240 }
241 const auto dataPointer = static_cast<T*>(&(bvector[0][0]));
242 copyToHost(dataPointer, m_numberOfElements);
243 }
244
252 void copyFromHost(const T* dataPointer, size_t numberOfElements)
253 {
254 if (numberOfElements > size()) {
255 OPM_THROW(std::runtime_error,
256 fmt::format("Requesting to copy too many elements. Buffer has {} elements, while {} was requested.",
257 size(),
258 numberOfElements));
259 }
260 detail::gpuMemcpyHostToDevice(data(), dataPointer, numberOfElements);
261 }
262
270 void copyToHost(T* dataPointer, size_t numberOfElements) const
271 {
272 assertSameSize(numberOfElements);
273 detail::gpuMemcpyDeviceToHost(dataPointer, data(), numberOfElements);
274 }
275
283 void copyFromHost(const std::vector<T>& data)
284 {
285 assertSameSize(data.size());
286
287 if (data.empty()) {
288 return;
289 }
290
291 if constexpr (std::is_same_v<T, bool>)
292 {
293 auto tmp = std::make_unique<bool[]>(data.size());
294 for (size_t i = 0; i < data.size(); ++i) {
295 tmp[i] = static_cast<bool>(data[i]);
296 }
297 copyFromHost(tmp.get(), data.size());
298 }
299 else {
300 copyFromHost(data.data(), data.size());
301 }
302 }
303
311 void copyToHost(std::vector<T>& data) const
312 {
313 assertSameSize(data.size());
314
315 if (data.empty()) {
316 return;
317 }
318
319 if constexpr (std::is_same_v<T, bool>)
320 {
321 auto tmp = std::make_unique<bool[]>(data.size());
322 copyToHost(tmp.get(), data.size());
323 for (size_t i = 0; i < data.size(); ++i) {
324 data[i] = static_cast<bool>(tmp[i]);
325 }
326 return;
327 }
328 else {
329 copyToHost(data.data(), data.size());
330 }
331 }
332
343 void copyFromHostAsync(const T* dataPointer, size_t numberOfElements, cudaStream_t stream = detail::DEFAULT_STREAM)
344 {
345 if (numberOfElements > size()) {
346 OPM_THROW(std::runtime_error,
347 fmt::format("Requesting to copy too many elements. Buffer has {} elements, while {} was requested.",
348 size(),
349 numberOfElements));
350 }
351 // Asynchronous copy. CUDA runtime will use pinned memory if dataPointer is in a registered region.
352 detail::gpuMemcpyHostToDeviceAsync(data(), dataPointer, numberOfElements, stream);
353 }
354
365 void copyToHostAsync(T* dataPointer, size_t numberOfElements, cudaStream_t stream = detail::DEFAULT_STREAM) const
366 {
367 assertSameSize(numberOfElements);
368 detail::gpuMemcpyDeviceToHostAsync(dataPointer, data(), numberOfElements, stream);
369 }
370
375 size_type size() const
376 {
377 return m_numberOfElements;
378 }
379
384 void resize(size_t newSize)
385 {
386 if (newSize < 1) {
387 OPM_THROW(std::invalid_argument, "Setting a GpuBuffer size to a non-positive number is not allowed");
388 }
389
390 if (newSize == m_numberOfElements) {
391 return;
392 }
393
394 if (m_numberOfElements == 0) {
395 // We have no data, so we can just allocate new memory
396 OPM_GPU_SAFE_CALL(cudaMalloc(&m_dataOnDevice, sizeof(T) * newSize));
397 }
398 else {
399 // Allocate memory for temporary buffer
400 T* tmpBuffer = nullptr;
401 OPM_GPU_SAFE_CALL(cudaMalloc(&tmpBuffer, sizeof(T) * newSize));
402
403 // Move the data from the old to the new buffer with truncation
404 size_t sizeOfMove = std::min({m_numberOfElements, newSize});
405 detail::gpuMemcpyDeviceToDevice(tmpBuffer, m_dataOnDevice, sizeOfMove);
406
407 // free the old buffer
408 OPM_GPU_SAFE_CALL(cudaFree(m_dataOnDevice));
409
410 // swap the buffers
411 m_dataOnDevice = tmpBuffer;
412 }
413
414 // update size
415 m_numberOfElements = newSize;
416 }
417
422 std::vector<T> asStdVector() const
423 {
424 std::vector<T> temporary(m_numberOfElements);
425 copyToHost(temporary);
426 return temporary;
427 }
428
429private:
430 T* m_dataOnDevice = nullptr;
431 size_t m_numberOfElements = 0;
432
433 void assertSameSize(const GpuBuffer<T>& other) const
434 {
435 assertSameSize(other.m_numberOfElements);
436 }
437
438 void assertSameSize(size_t size) const
439 {
440 if (size != m_numberOfElements) {
441 OPM_THROW(std::invalid_argument,
442 fmt::format(fmt::runtime("Given buffer has {}, while we have {}."),
443 size, m_numberOfElements));
444 }
445 }
446
447 void assertHasElements() const
448 {
449 if (m_numberOfElements <= 0) {
450 OPM_THROW(std::invalid_argument, "We have 0 elements");
451 }
452 }
453};
454
455template <class T>
456GpuView<T> make_view(GpuBuffer<T>& buf) {
457 return GpuView<T>(buf.data(), buf.size());
458}
459
460template <class T>
461GpuView<const T> make_view(const GpuBuffer<T>& buf) {
462 return GpuView<const T>(buf.data(), buf.size());
463}
464
465} // namespace Opm::gpuistl
466#endif
#define OPM_GPU_SAFE_CALL(expression)
OPM_GPU_SAFE_CALL checks the return type of the GPU expression (function call) and throws an exceptio...
Definition: gpu_safe_call.hpp:164
#define OPM_GPU_WARN_IF_ERROR(expression)
OPM_GPU_WARN_IF_ERROR checks the return type of the GPU expression (function call) and issues a warni...
Definition: gpu_safe_call.hpp:185
void gpuMemcpyHostToDeviceAsync(T *dstDevice, const T *srcHost, std::size_t count, cudaStream_t stream)
gpuMemcpyHostToDeviceAsync copies count elements of type T from host to device asynchronously.
Definition: gpu_memcpy.hpp:123
void gpuMemcpyHostToDevice(T *dstDevice, const T *srcHost, std::size_t count)
gpuMemcpyHostToDevice copies count elements of type T from host to device.
Definition: gpu_memcpy.hpp:46
void gpuMemcpyDeviceToHostAsync(T *dstHost, const T *srcDevice, std::size_t count, cudaStream_t stream)
gpuMemcpyDeviceToHostAsync copies count elements of type T from device to host asynchronously.
Definition: gpu_memcpy.hpp:155
constexpr cudaStream_t DEFAULT_STREAM
The default GPU stream (stream 0)
Definition: gpu_constants.hpp:31
void gpuMemcpyDeviceToHost(T *dstHost, const T *srcDevice, std::size_t count)
gpuMemcpyDeviceToHost copies count elements of type T from device to host.
Definition: gpu_memcpy.hpp:69
void gpuMemcpyDeviceToDevice(T *dstDevice, const T *srcDevice, std::size_t count)
gpuMemcpyDeviceToDevice copies count elements of type T from device to device.
Definition: gpu_memcpy.hpp:92
A small, fixed‑dimension MiniVector class backed by std::array that can be used in both host and CUDA...
Definition: GpuFlowProblem.hpp:48
inline ::Opm::NoThermalLawManager make_view(::Opm::NoThermalLawManager &)
make_view overload for the no-op thermal manager.
Definition: GpuFlowProblem.hpp:101