peclet-dem 1.1.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 <Kokkos_Core.hpp>
26#include <vector>
27
28#include "contact_preprocessing.hpp" // CpExec/CpMem
29
30namespace peclet::dem {
31
36 Kokkos::View<const int*, CpMem> offsDev;
37 Kokkos::View<unsigned*, CpMem> bar;
38 int maxBucket = 0;
39};
40
46 float tol;
47 bool strictLess; // position loop breaks on res < tol; velocity loops on res <= tol
48 // Diagnostics only (docs/contact_solve_framework.md §12 S12): a device scalar that receives the
49 // iterations the loop ran (the host loop's count). nullptr = not written; the driver passes it
50 // only with Particles::iterCounters on and reads it back only then (no extra fence or copy
51 // otherwise).
52 int* iters = nullptr;
53};
54
63inline bool demFusedWanted(bool graphReplayAvailable, int mode) {
64#ifdef KOKKOS_ENABLE_CUDA
65 return mode < 0 ? !graphReplayAvailable : mode == 1;
66#else
67 (void)graphReplayAvailable;
68 (void)mode;
69 return false;
70#endif
71}
72
73#ifdef KOKKOS_ENABLE_CUDA
74
85__device__ inline void demGridBarrier(unsigned* count, unsigned k) {
86 __syncthreads(); // this block's work items are done
87 if (threadIdx.x == 0) {
88 __threadfence(); // publish this block's writes before declaring arrival
89 atomicAdd(count, 1u);
90 const unsigned target = (k + 1u) * gridDim.x;
91 while (*reinterpret_cast<volatile unsigned*>(count) < target) {
92 }
93 __threadfence(); // acquire the other blocks' writes
94 }
95 __syncthreads(); // release the block into the next colour
96}
97
100inline constexpr int kFusedBarWords = 32769; // 4096 blocks * 8 + 1
101
106template <class Sweep>
107__global__ void demFusedColorSweepK(Sweep f, Kokkos::View<const int*, CpMem> perm,
108 Kokkos::View<const int*, CpMem> offs, int numColors,
109 unsigned* bar) {
110 const int stride = gridDim.x * blockDim.x;
111 const int tid = blockIdx.x * blockDim.x + threadIdx.x;
112 unsigned k = 0;
113 for (int c = 0; c < numColors; ++c) {
114 const int b = offs(c), e = offs(c + 1);
115 if (b == e)
116 continue;
117 for (int i = b + tid; i < e; i += stride)
118 f.solveOne(perm(i));
119 if (c + 1 < numColors)
120 demGridBarrier(bar, k++);
121 }
122}
123
129template <class Sweep>
130__global__ void demFusedSweepLoopK(Sweep f, Kokkos::View<const int*, CpMem> perm,
131 Kokkos::View<const int*, CpMem> offs, int numColors,
132 int maxIters, float tol, bool strictLess, float* res,
133 unsigned* bar, int* iters) {
134 const int stride = gridDim.x * blockDim.x;
135 const int tid = blockIdx.x * blockDim.x + threadIdx.x;
136 unsigned k = 0;
137 int ran = 0; // the host loop's iteration count (the break is uniform across blocks)
138 for (int it = 0; it < maxIters; ++it) {
139 ran = it + 1;
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 if (iters != nullptr && tid == 0)
157 *iters = ran; // diagnostics (§12 S12): a store, no fence; read back only when enabled
158}
159
160inline constexpr int kFusedBlock = 256;
161
165template <class Kernel>
166inline int demFusedMaxGrid(Kernel kernel) {
167 static const int maxGrid = [kernel] {
168 int perSm = 0;
169 if (cudaOccupancyMaxActiveBlocksPerMultiprocessor(&perSm, kernel, kFusedBlock, 0) !=
170 cudaSuccess) {
171 (void)cudaGetLastError();
172 return 0;
173 }
174 int dev = 0, sm = 0;
175 if (cudaGetDevice(&dev) != cudaSuccess ||
176 cudaDeviceGetAttribute(&sm, cudaDevAttrMultiProcessorCount, dev) != cudaSuccess) {
177 (void)cudaGetLastError();
178 return 0;
179 }
180 return perSm * sm;
181 }();
182 return maxGrid;
183}
184
188template <class Sweep>
189inline bool demLaunchFusedColorSweep(CpExec& space, const Sweep& f,
190 Kokkos::View<const int*, CpMem> perm, const FusedSweepCtx& ctx,
191 int numColors) {
192 const int maxGrid = std::min(demFusedMaxGrid(demFusedColorSweepK<Sweep>),
193 (static_cast<int>(ctx.bar.extent(0)) - 1) / 8);
194 if (maxGrid <= 0 || numColors <= 0 || ctx.maxBucket <= 0)
195 return false;
196 const int want = (ctx.maxBucket + kFusedBlock - 1) / kFusedBlock;
197 const int grid = want < maxGrid ? want : maxGrid;
198 cudaStream_t str = space.cuda_stream();
199 cudaMemsetAsync(ctx.bar.data(), 0, (static_cast<std::size_t>(grid) * 8 + 1) * sizeof(unsigned),
200 str);
201 demFusedColorSweepK<Sweep>
202 <<<grid, kFusedBlock, 0, str>>>(f, perm, ctx.offsDev, numColors, ctx.bar.data());
203 return true;
204}
205
209template <class Sweep>
210inline bool demLaunchFusedSweepLoop(CpExec& space, const Sweep& f,
211 Kokkos::View<const int*, CpMem> perm, const FusedSweepCtx& ctx,
212 int numColors, const FusedLoopSpec& spec, float* res) {
213 const int maxGrid = std::min(demFusedMaxGrid(demFusedSweepLoopK<Sweep>),
214 (static_cast<int>(ctx.bar.extent(0)) - 1) / 8);
215 if (maxGrid <= 0 || numColors <= 0 || ctx.maxBucket <= 0 || res == nullptr)
216 return false;
217 const int want = (ctx.maxBucket + kFusedBlock - 1) / kFusedBlock;
218 const int grid = want < maxGrid ? want : maxGrid;
219 cudaStream_t str = space.cuda_stream();
220 cudaMemsetAsync(ctx.bar.data(), 0, (static_cast<std::size_t>(grid) * 8 + 1) * sizeof(unsigned),
221 str);
222 demFusedSweepLoopK<Sweep><<<grid, kFusedBlock, 0, str>>>(f, perm, ctx.offsDev, numColors,
223 spec.maxIters, spec.tol, spec.strictLess,
224 res, ctx.bar.data(), spec.iters);
225 return true;
226}
227
228#endif // KOKKOS_ENABLE_CUDA
229
233inline FusedSweepCtx demMakeFusedCtx(CpExec& space, const std::vector<int>& offs,
234 Kokkos::View<int*, CpMem> offsDev,
235 Kokkos::View<unsigned*, CpMem> bar) {
236 FusedSweepCtx ctx;
237#ifdef KOKKOS_ENABLE_CUDA
238 if (offs.size() < 2 || offs.size() > offsDev.extent(0) + static_cast<std::size_t>(0))
239 return ctx;
240 int maxBucket = 0;
241 for (std::size_t c = 0; c + 1 < offs.size(); ++c)
242 maxBucket = std::max(maxBucket, offs[c + 1] - offs[c]);
243 if (maxBucket <= 0)
244 return ctx;
245 const Kokkos::View<const int*, Kokkos::HostSpace, Kokkos::MemoryUnmanaged> h(offs.data(),
246 offs.size());
247 auto d = Kokkos::subview(offsDev, Kokkos::pair<std::size_t, std::size_t>(0, offs.size()));
248 Kokkos::deep_copy(space, d, h);
249 ctx.offsDev = Kokkos::subview(Kokkos::View<const int*, CpMem>(offsDev),
250 Kokkos::pair<std::size_t, std::size_t>(0, offs.size()));
251 ctx.bar = bar;
252 ctx.maxBucket = maxBucket;
253#else
254 (void)space;
255 (void)offs;
256 (void)offsDev;
257 (void)bar;
258#endif
259 return ctx;
260}
261
262} // namespace peclet::dem
263
264#endif // DEM_SOLVER_FUSED_HPP
dem — portable (Kokkos) contact->manifold reduction, replacing the thrust-based reduce_contacts_to_ma...
bool demFusedWanted(bool graphReplayAvailable, int mode)
Fused-sweep policy.
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