peclet-dem 0.4.0
Performance-portable XPBD Discrete Element Method (Kokkos + ArborX)
Loading...
Searching...
No Matches
solver_fused.hpp
Go to the documentation of this file.
1
20#ifndef DEM_SOLVER_FUSED_HPP
21#define DEM_SOLVER_FUSED_HPP
22
23#include <algorithm>
24#include <cstdlib>
25#include <vector>
26
27#include <Kokkos_Core.hpp>
28
29#include "contact_preprocessing.hpp" // CpExec/CpMem
30
31namespace peclet::dem {
32
37 Kokkos::View<const int*, CpMem> offsDev;
38 Kokkos::View<unsigned*, CpMem> bar;
39 int maxBucket = 0;
40};
41
47 float tol;
48 bool strictLess; // position loop breaks on res < tol; velocity loops on res <= tol
49};
50
59inline bool demFusedWanted(bool graphReplayAvailable) {
60#ifdef KOKKOS_ENABLE_CUDA
61 static const int mode = [] {
62 if (std::getenv("PECLET_DEM_NO_FUSED"))
63 return 0;
64 if (std::getenv("PECLET_DEM_FUSED"))
65 return 1;
66 return -1; // auto
67 }();
68 return mode < 0 ? !graphReplayAvailable : mode == 1;
69#else
70 (void)graphReplayAvailable;
71 return false;
72#endif
73}
74
75#ifdef KOKKOS_ENABLE_CUDA
76
87__device__ inline void demGridBarrier(unsigned* count, unsigned k) {
88 __syncthreads(); // this block's work items are done
89 if (threadIdx.x == 0) {
90 __threadfence(); // publish this block's writes before declaring arrival
91 atomicAdd(count, 1u);
92 const unsigned target = (k + 1u) * gridDim.x;
93 while (*reinterpret_cast<volatile unsigned*>(count) < target) {
94 }
95 __threadfence(); // acquire the other blocks' writes
96 }
97 __syncthreads(); // release the block into the next colour
98}
99
102inline constexpr int kFusedBarWords = 32769; // 4096 blocks * 8 + 1
103
108template <class Sweep>
109__global__ void demFusedColorSweepK(Sweep f, Kokkos::View<const int*, CpMem> perm,
110 Kokkos::View<const int*, CpMem> offs, int numColors,
111 unsigned* bar) {
112 const int stride = gridDim.x * blockDim.x;
113 const int tid = blockIdx.x * blockDim.x + threadIdx.x;
114 unsigned k = 0;
115 for (int c = 0; c < numColors; ++c) {
116 const int b = offs(c), e = offs(c + 1);
117 if (b == e)
118 continue;
119 for (int i = b + tid; i < e; i += stride)
120 f.solveOne(perm(i));
121 if (c + 1 < numColors)
122 demGridBarrier(bar, k++);
123 }
124}
125
131template <class Sweep>
132__global__ void demFusedSweepLoopK(Sweep f, Kokkos::View<const int*, CpMem> perm,
133 Kokkos::View<const int*, CpMem> offs, int numColors,
134 int maxIters, float tol, bool strictLess, float* res,
135 unsigned* bar) {
136 const int stride = gridDim.x * blockDim.x;
137 const int tid = blockIdx.x * blockDim.x + threadIdx.x;
138 unsigned k = 0;
139 for (int it = 0; it < maxIters; ++it) {
140 if (tid == 0)
141 *res = 0.0f;
142 demGridBarrier(bar, k++);
143 for (int c = 0; c < numColors; ++c) {
144 const int b = offs(c), e = offs(c + 1);
145 if (b == e)
146 continue;
147 for (int i = b + tid; i < e; i += stride)
148 f.solveOne(perm(i));
149 demGridBarrier(bar, k++);
150 }
151 const float r = *reinterpret_cast<volatile float*>(res);
152 if (strictLess ? (r < tol) : (r <= tol))
153 break;
154 demGridBarrier(bar, k++); // every block has read res before the next zero
155 }
156}
157
158inline constexpr int kFusedBlock = 256;
159
164inline int demFusedGridCap() {
165 static const int cap = [] {
166 const char* e = std::getenv("PECLET_DEM_FUSED_GRID");
167 return e ? std::atoi(e) : 1 << 30;
168 }();
169 return cap;
170}
171
175template <class Kernel>
176inline int demFusedMaxGrid(Kernel kernel) {
177 static const int maxGrid = [kernel] {
178 int perSm = 0;
179 if (cudaOccupancyMaxActiveBlocksPerMultiprocessor(&perSm, kernel, kFusedBlock, 0) !=
180 cudaSuccess) {
181 (void)cudaGetLastError();
182 return 0;
183 }
184 int dev = 0, sm = 0;
185 if (cudaGetDevice(&dev) != cudaSuccess ||
186 cudaDeviceGetAttribute(&sm, cudaDevAttrMultiProcessorCount, dev) != cudaSuccess) {
187 (void)cudaGetLastError();
188 return 0;
189 }
190 return perSm * sm;
191 }();
192 return maxGrid;
193}
194
198template <class Sweep>
199inline bool demLaunchFusedColorSweep(CpExec& space, const Sweep& f,
200 Kokkos::View<const int*, CpMem> perm,
201 const FusedSweepCtx& ctx, int numColors) {
202 const int maxGrid = std::min(demFusedMaxGrid(demFusedColorSweepK<Sweep>),
203 (static_cast<int>(ctx.bar.extent(0)) - 1) / 8);
204 if (maxGrid <= 0 || numColors <= 0 || ctx.maxBucket <= 0)
205 return false;
206 const int want = std::min((ctx.maxBucket + kFusedBlock - 1) / kFusedBlock,
207 std::max(1, demFusedGridCap()));
208 const int grid = want < maxGrid ? want : maxGrid;
209 cudaStream_t str = space.cuda_stream();
210 cudaMemsetAsync(ctx.bar.data(), 0, (static_cast<std::size_t>(grid) * 8 + 1) * sizeof(unsigned),
211 str);
212 demFusedColorSweepK<Sweep>
213 <<<grid, kFusedBlock, 0, str>>>(f, perm, ctx.offsDev, numColors, ctx.bar.data());
214 return true;
215}
216
220template <class Sweep>
221inline bool demLaunchFusedSweepLoop(CpExec& space, const Sweep& f,
222 Kokkos::View<const int*, CpMem> perm,
223 const FusedSweepCtx& ctx, int numColors,
224 const FusedLoopSpec& spec, float* res) {
225 const int maxGrid = std::min(demFusedMaxGrid(demFusedSweepLoopK<Sweep>),
226 (static_cast<int>(ctx.bar.extent(0)) - 1) / 8);
227 if (maxGrid <= 0 || numColors <= 0 || ctx.maxBucket <= 0 || res == nullptr)
228 return false;
229 const int want = std::min((ctx.maxBucket + kFusedBlock - 1) / kFusedBlock,
230 std::max(1, demFusedGridCap()));
231 const int grid = want < maxGrid ? want : maxGrid;
232 cudaStream_t str = space.cuda_stream();
233 cudaMemsetAsync(ctx.bar.data(), 0, (static_cast<std::size_t>(grid) * 8 + 1) * sizeof(unsigned),
234 str);
235 demFusedSweepLoopK<Sweep><<<grid, kFusedBlock, 0, str>>>(f, perm, ctx.offsDev, numColors,
236 spec.maxIters, spec.tol,
237 spec.strictLess, res, ctx.bar.data());
238 return true;
239}
240
241#endif // KOKKOS_ENABLE_CUDA
242
246inline FusedSweepCtx demMakeFusedCtx(CpExec& space, const std::vector<int>& offs,
247 Kokkos::View<int*, CpMem> offsDev,
248 Kokkos::View<unsigned*, CpMem> bar) {
249 FusedSweepCtx ctx;
250#ifdef KOKKOS_ENABLE_CUDA
251 if (offs.size() < 2 || offs.size() > offsDev.extent(0) + static_cast<std::size_t>(0))
252 return ctx;
253 int maxBucket = 0;
254 for (std::size_t c = 0; c + 1 < offs.size(); ++c)
255 maxBucket = std::max(maxBucket, offs[c + 1] - offs[c]);
256 if (maxBucket <= 0)
257 return ctx;
258 const Kokkos::View<const int*, Kokkos::HostSpace, Kokkos::MemoryUnmanaged> h(
259 offs.data(), offs.size());
260 auto d = Kokkos::subview(offsDev, Kokkos::pair<std::size_t, std::size_t>(0, offs.size()));
261 Kokkos::deep_copy(space, d, h);
262 ctx.offsDev = Kokkos::subview(Kokkos::View<const int*, CpMem>(offsDev),
263 Kokkos::pair<std::size_t, std::size_t>(0, offs.size()));
264 ctx.bar = bar;
265 ctx.maxBucket = maxBucket;
266#else
267 (void)space;
268 (void)offs;
269 (void)offsDev;
270 (void)bar;
271#endif
272 return ctx;
273}
274
275} // namespace peclet::dem
276
277#endif // DEM_SOLVER_FUSED_HPP
dem — portable (Kokkos) contact->manifold reduction, replacing the thrust-based reduce_contacts_to_ma...
bool demFusedWanted(bool graphReplayAvailable)
Fused-sweep policy (read once).
FusedSweepCtx demMakeFusedCtx(CpExec &space, const std::vector< int > &offs, Kokkos::View< int *, CpMem > offsDev, Kokkos::View< unsigned *, CpMem > bar)
Fill a FusedSweepCtx from buildColorBucketsKokkos's host offsets: async-upload them into the pooled d...
Kokkos::DefaultExecutionSpace CpExec
Device-side iteration loop: run up to maxIters sweeps of the colour classes inside ONE kernel,...
Device-side context for a fused colour sweep: the colour offsets (numColors+1, uploaded from buildCol...
Kokkos::View< const int *, CpMem > offsDev
Kokkos::View< unsigned *, CpMem > bar