Cholla 3.0.1-dev
Cholla - Massively parallel hydro on GPUs
Loading...
Searching...
No Matches
kernel.h
1#pragma once
2/* Define the main kernel that does most of the heavy lifting.
3 *
4 * This is primarily defined in a header so that we can directly test it.
5 */
6
7#include <climits>
8#include <type_traits>
9
10#include "../feedback/feedback.h"
11#include "../global/global.h"
12#include "../utils/DeviceVector.h"
13#include "../utils/basic_structs.h"
14#include "../utils/error_handling.h"
15#include "../utils/gpu.hpp"
16#include "../utils/reduction_utilities.h"
17
18// uncomment the following line for debugging
19// #define FEEDBACK_LOG_INDIVIDUAL 1
20
21#ifndef FEEDBACK_LOG_INDIVIDUAL
22 #define FEEDBACK_LOG_INDIVIDUAL 0
23#endif
24
25#define TPB_FEEDBACK 128
26
27// first we define some basic details, this could theoretically go into a separate file
28// ====================================================================================
29
30// The following is only here to simplify testing. In the future it may make sense to move it to a different header
31namespace feedback_details
32{
33
34/* Group together all of the particle-property arguments */
36 part_int_t n_local;
37 const part_int_t* id_dev;
38 const Real* pos_x_dev;
39 const Real* pos_y_dev;
40 const Real* pos_z_dev;
41 const Real* vel_x_dev;
42 const Real* vel_y_dev;
43 const Real* vel_z_dev;
44 Real* mass_dev;
45 const Real* age_dev;
46};
47
48/* Group together all of arguments describing the spatial field structure. */
50 Real xMin;
51 Real yMin;
52 Real zMin;
53 Real xMax;
54 Real yMax;
55 Real zMax;
56 Real dx;
57 Real dy;
58 Real dz;
59 int nx_g;
60 int ny_g;
61 int nz_g;
62 int n_ghost;
63};
64
65/* Groups properties of the simulation's current (global) iteration cycle */
66struct CycleProps {
67 Real t;
68 Real dt;
69 int n_step;
70};
71
72} // namespace feedback_details
73
74// N
75
76namespace feedback_details
77{
78
79/* Specifies the stategy for handling star-particles with overlapping stencils */
80enum struct BoundaryStrategy {
81 excludeGhostParticle_ignoreStencilIssues,
85 excludeGhostParticle_snapActiveStencil
89};
90
91} // namespace feedback_details
92
93// STENCIL OVERLAPS
94// ----------------
95// We refer to the pattern of cells around a particle that are affected by a given feedback perscription
96// as a stencil
97//
98// Because we use separate GPU threads to simultaneously process the impact of feedback from separate
99// particles on the local fluid fields, care must be taken when nearby particles have overlapping
100// stencils.
101//
102// Our chosen approach
103// -------------------
104// When there is overlap, we essentially handle feedback one-at-a-time where the order is based on some
105// deterministic particle property (nominally the particle id).
106//
107// Essentially, we first pre-register the stencils of all particles undergoing feedback in a "mask".
108// Then we make a pass through all of the particles with pending feedback.
109// - For each particle with pending feedback, we use the previously-constructed "mask" to check if
110// its stencil overlaps with a stencil of a different particle (also with pending feedback) that
111// has a larger particle id.
112// - if it doesn't, we perform feedback now!
113// - otherwise, we defer feedback until the next "pass" (and register its stencil in the "mask" used
114// for the next pass)
115// We make additional passes until we have applied all feedback.
116//
117// In the best case scenario (no overlap), we just do one pass. In the pathological worst case (all
118// particles are directly on top of each other), the number of passes is equal to the number of
119// particles with pending feedback.
120//
121// Alternative Options
122// -------------------
123// For a small subset of prescriptions, it's possible to handle overlapping regions by using atomic
124// operations to update the fluid fields. This only works if you know how much each quantity will change
125// ahead of time (i.e. the changes are independent of the current values). However, because we primarily
126// parameterize the fluid's properties in terms of its mass density, momenta densities, and total energy
127// densities, the prescriptions that can be handled in this way are VERY limited. This approach is only
128// applicable for prescriptions
129// - that only inject a fixed amount of thermal energy density
130// - OR are fairly pathological. Here are 2 simple examples:
131// 1. if the prescription injects momentum (while not affecting mass), then assumptions need to be made
132// that resulting changes to the kinetic energy are exactly balanced by changes to the thermal energy
133// (this is because you can't know how the kinetic energy density will change unless you you know the
134// current momentum and current density).
135// 2. if the prescription injects mass (while not affecting momentum -- effectively reducing the bulk
136// velocity), makes a similar assumption that any changes to the kinetic energy density are exactly
137// balanced by changes to the thermal energy
138// NOTE: If we adopted a strategy where we convert the total energy to the thermal energy, then apply all
139// feedback, and then at the end recompute the total energy afterwards this opens more options. It's worth
140// considering in the future (although it is less flexible than our chosen approach).
141
142// The following definitions are only in a header file to simplify testing.
143namespace feedback_details
144{
145
146// this is a temporary class meant to mimic shared_ptr. Longer term, I plan to use a class that mimics shared_ptr
147// - the ideology of a unique-ptr existing both on the host and a device is a little weird - but it is sound as
148// long as the unique-ptr itself does not persist on the device outside of kernel calls (of course the data
149// unique-ptr can/will persist on the device for longer periods of time)
150template <typename T>
152{
153 static_assert(std::is_trivially_copyable_v<T> and (!std::is_pointer_v<T>) and (!std::is_reference_v<T>));
154 T* ptr_;
155
156 public:
157 /* default constructor. Makes an empty shared pointer */
158 __host__ __device__ SimpleUniqueDevPtr() : ptr_(nullptr) {}
159 __host__ __device__ SimpleUniqueDevPtr(std::nullptr_t) : SimpleUniqueDevPtr() {}
160
161 /* Allocates a new unique pointer that holds ``count`` entries of ``T`` */
162 __host__ SimpleUniqueDevPtr(std::size_t count)
163 {
164 CHOLLA_ASSERT(count > 0, "count must be a positive integer");
165 GPU_Error_Check(cudaMalloc(&ptr_, count * sizeof(T)));
166 }
167
168 /* destructor. The memory is only deallocated when this is executed on the host */
169 __host__ __device__ ~SimpleUniqueDevPtr()
170 {
171#if !((defined(__HIP_DEVICE_COMPILE__) && defined(O_HIP)) || (defined(__CUDA_ARCH__) && !defined(O_HIP)))
172 GPU_Error_Check(cudaDeviceSynchronize()); // ensure we can't deallocate a ptr that a kernel is currently using
173 if (ptr_ != nullptr) GPU_Error_Check(cudaFree(ptr_));
174#endif
175 }
176
178 SimpleUniqueDevPtr<T>& operator=(const SimpleUniqueDevPtr<T>&) = delete;
179 SimpleUniqueDevPtr(SimpleUniqueDevPtr<T>&& other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
180 SimpleUniqueDevPtr<T>& operator=(SimpleUniqueDevPtr<T>&& other) noexcept
181 {
182 this->swap(other);
183 return *this;
184 }
185
186 /* array-element-access of the underlying pointer (invokes undefined behavior when ``this`` is empty) */
187 __device__ __forceinline__ T& operator[](std::ptrdiff_t idx) const noexcept { return ptr_[idx]; }
188
189 /* dereference the stored pointer (invokes undefined behavior when ``this`` is empty) */
190 __device__ __forceinline__ T& operator*() const noexcept { return *ptr_; }
191
192 /* accessor-method that retrieves the stored pointer */
193 __host__ __device__ __forceinline__ T* get() const noexcept { return ptr_; }
194
195 /* Provides support for checking whether ``this`` is empty. */
196 __host__ __device__ __forceinline__ explicit operator bool() const noexcept { return ptr_ != nullptr; }
197
198 /* swap the contents of ``this`` with ``other`` */
199 __host__ __device__ void swap(SimpleUniqueDevPtr<T>& other) noexcept
200 {
201 T* tmp = this->ptr_;
202 this->ptr_ = other.ptr_;
203 other.ptr_ = tmp;
204 }
205};
206
207/* Specifies the stategy for handling star-particles with overlapping stencils */
208enum struct OverlapStrat {
209 ignore, /*<! simply ignore that there are overlaps. Schedule everything at once (useful for profiling) */
210 sequential /*<! Process feedback for all overlapping stencils (in order of the increasing particle ids) */
211};
212
213/* Class that implements most of the logic (and tracks associated data) for scheduling feedback from particles.
214 * It's essentially a state-machine.
215 *
216 * As a shorthand, lets refer to the collection of particles who need to have their feedback during a given
217 * simulation cycle, the "designated-feedback-particles"
218 *
219 * When configured with OverlapStrat::sequential (the primary use-case of this class), "designated-feedback-particles"
220 * with overlapping stencils will be scheduled sequentially. The intention is for an instance of this class, lets
221 * call it `ov_scheduler`, to be used in the following control flow:
222 *
223 * - at the start of the relevant kernel, call `ov_scheduler.Reset_State`
224 * - then iterate over all of the "designated-feedback-particles" (all particles that need to have their feedback
225 * applied in the current kernel call). For each of these particles, call
226 * `ov_scheduler.Register_Pending_Particle_Feedback`
227 * - now enter a while-loop where `ov_scheduler.Prepare_Next_Pass` is evaluated as the condition-expression
228 * - we refer to each evaluation of the loop body as a "pass".
229 * - The loop-body should consist of iterating over all of the "designated-feedback-particles" (in other words, each
230 * evaluation of the loop body corresponds to a "pass" through the "designated-feedback-particles"). For each of
231 * these particles, evaluate `ov_scheduler.Is_Scheduled_And_Update`
232 * - that method will return whether that is scheduled to have its feedback applied right now. It will also update
233 * the internal state of
234 *
235 * \note
236 * The code would probably be faster if we specified OverlapStrat as a template argument, but everything is already
237 * fairly template-heavy.
238 *
239 * \note
240 * Problems could arise if you passed particle information to this class's methods (after you have reset the state)
241 * that aren't part of the "designated-feedback-particles".
242 */
244{
245 private:
246 /* the overlap strategy */
247 OverlapStrat strat_ = OverlapStrat::ignore;
248 /* counts the number of "passes" */
249 part_int_t pass_count_ = 0;
250 /* the total size of each shared register */
251 std::size_t mask_size_ = 0;
252
253 // the following attributes are all pointers to global device memory
254
256 /* the "masks" each are each pointers to have the same sizes as a fluid-field (including ghost zones) - each value
257 * corresponds to a distinct cell in the simulation.
258 *
259 * In each mask, the value at each location will either store ``OverlapScheduler::DFLT_VAL`` or the minimum particle
260 * id of a particle with pending feedback whose stencil "may" overlap with the location.
261 *
262 * In a given "pass", the values in curPass_mask_ are used to identify which particles can have their feedback applied
263 * in the current pass and the values of nextPass_mask_ are updated to help which particles can have their feedback
264 * applied in a future cycle.
265 */
266 SimpleUniqueDevPtr<part_int_t> curPass_mask_ = nullptr;
267 SimpleUniqueDevPtr<part_int_t> nextPass_mask_ = nullptr;
269
270 /* pointer to a global memory address that is used to track whether there are any particles with pending feedback that
271 * will need to be applied in a future "pass".
272 *
273 * At the start of each "pass" this is initialized to zero and it is gradually updated over the course of the "pass"
274 * (as particles with pending feedback are encountered that must be handled in a future "pass"). If this has a
275 * non-zero value at the end of a given "pass", then another "pass" is required.
276 */
277 SimpleUniqueDevPtr<int> any_pending_particles_;
278
279 public:
280 /* the default value stored in a "mask".
281 *
282 * this is the max value representable by a 64-bit signed integer.
283 */
284 static inline constexpr part_int_t DFLT_VAL = 9223372036854775807;
285
286 /* default constructor */
287 __host__ __device__ OverlapScheduler()
288 : strat_(OverlapStrat::ignore),
289 pass_count_(0),
290 mask_size_(0),
291 curPass_mask_(nullptr),
292 nextPass_mask_(nullptr),
293 any_pending_particles_(nullptr)
294 {
295 }
296
297 /* Main constructor of OverlapScheduler.
298 *
299 * \note
300 * the data_storage argument MUST persist longer than the lifetime of this
301 * class. It manages the lifetime of the pointers used by this class.
302 */
303 __host__ OverlapScheduler(OverlapStrat strat, int ng_x, int ng_y, int ng_z) : OverlapScheduler()
304 {
305 this->strat_ = strat;
306 std::size_t mask_size = ng_x * ng_y * ng_z;
307
308 switch (strat) {
309 case OverlapStrat::ignore:
310 // this is all redundant, but we opt for explicitness
311 this->pass_count_ = 0;
312 this->mask_size_ = 0;
313 this->curPass_mask_ = nullptr;
314 this->nextPass_mask_ = nullptr;
315 this->any_pending_particles_ = nullptr;
316 break;
317 case OverlapStrat::sequential:
318 this->pass_count_ = 0;
319 this->mask_size_ = mask_size;
320 this->curPass_mask_ = SimpleUniqueDevPtr<part_int_t>(mask_size);
321 this->nextPass_mask_ = SimpleUniqueDevPtr<part_int_t>(mask_size);
322 this->any_pending_particles_ = SimpleUniqueDevPtr<int>(1);
323 break;
324 }
325 }
326
327 /* This must be called at the start of the kernel call where an OverlapScheduler will be used
328 *
329 * \note
330 * This is a collective operation that must be executed by all threads (throughout the entire
331 * grid) at once. Deadlocks will occur if this is executed in a conditional branch that some
332 * threads can't reach.
333 */
334 __device__ void Reset_State(const cooperative_groups::grid_group& g)
335 {
336 this->pass_count_ = 0;
337
338 if (this->strat_ != OverlapStrat::ignore) {
339 if (g.thread_rank() == 0) *(this->any_pending_particles_) = 0;
340 // in the above operation, it should't logically matter whether one or more thread modifies
341 // any_pending_particles_'s contents (but I suspect that it may affect performance)
342
343 OverlapScheduler::clear_mask(this->nextPass_mask_.get(), this->mask_size_);
344
345 g.sync(); // this sync is required to ensure that all threads across the grid are done
346 // clearing the mask (before we start mutating the mask)
347 }
348 }
349
350 /* try to prepare for the next pass through particles with pending feedback.
351 *
352 * \returns true if there are any particles with pending feedback remaining.
353 *
354 * \note
355 * This is a collective operation that must be executed by all threads (throughout the entire
356 * grid) at once. Deadlocks will occur if this is executed in a conditional branch that some
357 * threads can't reach.
358 */
359 __device__ bool Prepare_Next_Pass(const cooperative_groups::grid_group& g)
360 {
361 if (this->strat_ == OverlapStrat::ignore) { // in this scenario, only a single pass is required!
362 if (this->pass_count_ != 0) return false;
363 this->pass_count_ = 1;
364 return true;
365 }
366
367 // the rest of this logic is for OverlapStrat::sequential
368
369 g.sync(); // this is important! we need to be sure all threads across all thread-blocks are done
370 // completing any previous "passes" through the data to make sure all threads agree that
371 // another pass is required.
372
373 // if there are no particles with pending feedback, we are done! We can exit now
374 if (0 == *(this->any_pending_particles_)) return false;
375
376 // otherwise we need to continue on. Let's synchronize since we will be resetting the value of
377 // this->any_pending_particles_ (is this a place where we can use a memory fence?)
378 g.sync();
379 if (g.thread_rank() == 0) *(this->any_pending_particles_) = 0;
380 // in the above operation, it should't logically matter whether one or more thread modifies
381 // any_pending_particles_'s contents (but I suspect that it may affect performance)
382
383 // increment the total pass-count
384 this->pass_count_++;
385
386 // Finally prepare the masks for the next loop
387 this->curPass_mask_.swap(this->nextPass_mask_);
388 OverlapScheduler::clear_mask(this->nextPass_mask_.get(), this->mask_size_);
389
390 g.sync(); // this last sync is required to make sure all threads across the grid are done clearing
391 // the mask (before we start mutating the mask)
392 return true;
393 }
394
395 /* External users of OverlapScheduler must calls this during initial setup for each particle that will
396 * undergo feedback during the upcoming cycle.
397 *
398 * \note
399 * This is also used internally.
400 */
401 template <typename Prescription>
402 __device__ void Register_Pending_Particle_Feedback(Prescription p, long long int particle_id,
403 hydro_utilities::VectorXYZ<Real> pos_indU, int ng_x, int ng_y)
404 {
405 if (strat_ == OverlapStrat::ignore) return;
406
407 // record that a pass is necessary
408 atomicMax(this->any_pending_particles_.get(), 1);
409
410 static_assert(sizeof(long long int) == sizeof(part_int_t));
411 long long int* mask = (long long int*)(this->nextPass_mask_.get());
412 p.for_each_possible_overlap(
413 pos_indU[0], pos_indU[1], pos_indU[2], ng_x, ng_y,
414 [mask, particle_id](Real dummy_arg, int ind3d) -> void { atomicMin(mask + ind3d, particle_id); });
415 }
416
417 /* Checks whether a particle that applies feedback during the current cycle is scheduled to apply feedback right now
418 * (in the current "pass"). This function MAY also update the internal state to prepare for the schdeduler for future
419 * "passes".
420 *
421 * This will return `false` for particles that already applied feedback during the current simulation-cycle (but in a
422 * prior "pass") It will also return `false` if the specified particle's feedback is scheduled for a future pass.
423 */
424 template <typename Prescription>
425 __device__ bool Is_Scheduled_And_Update(Prescription p, part_int_t particle_id,
426 hydro_utilities::VectorXYZ<Real> pos_indU, int ng_x, int ng_y)
427 {
428 if (this->strat_ == OverlapStrat::ignore) return true;
429
430 // retrieve the minimum particle_id in the current zone
431 part_int_t min_id = OverlapScheduler::DFLT_VAL;
432 part_int_t* mask = this->curPass_mask_.get();
433
434 p.for_each_possible_overlap(
435 pos_indU[0], pos_indU[1], pos_indU[2], ng_x, ng_y,
436 [mask, &min_id](Real dummy_arg, int ind3d) -> void { min_id = min(min_id, mask[ind3d]); });
437
438 if (particle_id < min_id) { // feedback was applied in prior "pass"
439 return false;
440 } else if (particle_id == min_id) { // feedback is scheduled for current "pass"
441 return true;
442 } else { // particle feedback scheduled for future "pass"
443 // record that another pass is necessary & prepare the mask for the next pass
444 Register_Pending_Particle_Feedback(p, particle_id, pos_indU, ng_x, ng_y);
445 return false;
446 }
447 }
448
449 private:
450 /* To be called across all threads and blocks at once
451 *
452 * \note
453 * Assumes that blockDim.y, blockDim.z, gridDim.y, gridDim.z are all 1.
454 * We can fix this!
455 */
456 static __device__ void clear_mask(part_int_t* ptr, std::size_t len)
457 {
458 len *= std::size_t(ptr != nullptr);
459 const std::size_t start = blockIdx.x * blockDim.x + threadIdx.x;
460 const std::size_t loop_stride = blockDim.x * gridDim.x;
461 for (int i = start; i < len; i += loop_stride) {
462 ptr[i] = OverlapScheduler::DFLT_VAL;
463 }
464 }
465};
466
467__device__ __forceinline__ hydro_utilities::VectorXYZ<Real> Calc_Pos_IndU(
468 int i, const feedback_details::ParticleProps& particle_props,
469 const feedback_details::FieldSpatialProps& spatial_props)
470{
471 const int n_ghost = spatial_props.n_ghost;
472 return {(particle_props.pos_x_dev[i] - spatial_props.xMin) / spatial_props.dx + n_ghost,
473 (particle_props.pos_y_dev[i] - spatial_props.yMin) / spatial_props.dy + n_ghost,
474 (particle_props.pos_z_dev[i] - spatial_props.zMin) / spatial_props.dz + n_ghost};
475}
476
477/* Applies cluster feedback.
478 *
479 * \tparam FeedbackModel type that encapsulates the actual feedback prescription
480 * \tparam BdryStrat specifies the policy used for handling feedback stencils that overlap with boundaries of the active
481 * zone
482 *
483 * \param[in,out] particle_props Encodes the actual particle data needed for feedback. If there is any feedback, the
484 * relevant particle properties (like particle mass) will be updated during this call.
485 * \param[in] spatial_props Encodes spatial information about the local domain and the fields
486 * \param[in] cycle_props Encodes details about the simulation's current (global) iteration cycle
487 * \param[out] info An array that will is intended to accumulate summary details about the feedback during the course
488 * of this kernel call. This function assumes it has FBInfoLUT::LEN entries that are all initialized to 0. \param[out]
489 * conserved_dev pointer to the fluid-fields that will be updated during this function call. \param[in] An array of
490 * ``particle_props.n_local`` non-negative integers that specify the number of supernovae that are are scheduled to
491 * occur during the current cycle (for each particle). \param[in] ov_scheduler helps schedule feedback of particles
492 * with overlapping stencils.
493 */
494template <typename FeedbackModel, BoundaryStrategy BdryStrat>
495__global__ void Cluster_Feedback_Kernel(const feedback_details::ParticleProps particle_props,
496 const feedback_details::FieldSpatialProps spatial_props,
497 const feedback_details::CycleProps cycle_props, Real* info, Real* conserved_dev,
498 int* num_SN_dev, OverlapScheduler ov_scheduler)
499{
500 const int tid = threadIdx.x;
501 cooperative_groups::grid_group g = cooperative_groups::this_grid();
502
503 // initialize fb_model - this doesn't carry any state. It's just here to help with inference of
504 // the FeedbackModel types in all of the helper functions
505 FeedbackModel fb_model{};
506
507 // prologoue: setup buffer for collecting SN feedback information
508 __shared__ Real s_info[FBInfoLUT::LEN * TPB_FEEDBACK];
509 for (unsigned int cur_ind = 0; cur_ind < FBInfoLUT::LEN; cur_ind++) {
510 s_info[FBInfoLUT::LEN * tid + cur_ind] = 0;
511 }
512
513 // this lambda func returns true if particle is in-bounds and has at least 1 SNe
514 // - based on the value of the BdryStrat template-parameter, it may also modify the position
515 // that should be used when applying the feedback.
516 auto checkDontSkip_and_maybeRevisePos = [&spatial_props, num_SN_dev](int i,
518 const int n_ghost = spatial_props.n_ghost;
519
520 bool ignore = (((pos_indU[0] < n_ghost) or (pos_indU[0] >= (spatial_props.nx_g - n_ghost))) or
521 ((pos_indU[1] < n_ghost) or (pos_indU[1] >= (spatial_props.ny_g - n_ghost))) or
522 ((pos_indU[2] < n_ghost) or (pos_indU[2] >= (spatial_props.nz_g - n_ghost))));
523
524 // the branch-condition is determined at compile-time (since BdryStrat is a template parameter)
525 if (BdryStrat == BoundaryStrategy::excludeGhostParticle_snapActiveStencil) {
526 // overwrite pos_indU with the closest posititon, where stencil only includes active zones
527 // - if the stencil already just overlaps with active zone this should do nothing
528 // - it doesn't really matter if we alter the position of a particle outside of the active
529 // zone since we will always ignore that particle.
530 pos_indU = FeedbackModel::nearest_noGhostOverlap_pos(pos_indU, spatial_props.nx_g, spatial_props.ny_g,
531 spatial_props.nz_g, n_ghost);
532 }
533
534 return (not ignore) and (num_SN_dev[i] > 0);
535 };
536
537 // Prepare to iterate over the the list of particles
538 // - this is grid-strided loop. This is a common idiom that makes the kernel more flexible
539 // - If there are more local particles than threads, some threads will visit more than 1 particle
540 const int start = blockIdx.x * blockDim.x + threadIdx.x;
541 const int loop_stride = blockDim.x * gridDim.x;
542
543 // get ov_scheduler set up properly
544 ov_scheduler.Reset_State(g);
545 for (int i = start; i < particle_props.n_local; i += loop_stride) {
546 // compute the position in index-units (appropriate for a field with a ghost-zone)
547 // - an integer value corresponds to the left edge of a cell
548 hydro_utilities::VectorXYZ<Real> pos_indU = Calc_Pos_IndU(i, particle_props, spatial_props);
549
550 if (checkDontSkip_and_maybeRevisePos(i, pos_indU)) {
551 ov_scheduler.Register_Pending_Particle_Feedback(fb_model, (long long int)(particle_props.id_dev[i]), pos_indU,
552 spatial_props.nx_g, spatial_props.ny_g);
553 }
554 }
555
556 // do the main work.
557 while (ov_scheduler.Prepare_Next_Pass(g)) {
558 // if (g.thread_rank() == 0) kernel_printf("entered loop!\n");
559
560 for (int i = start; i < particle_props.n_local; i += loop_stride) {
561 // compute the position in index-units (appropriate for a field with a ghost-zone)
562 // - an integer value corresponds to the left edge of a cell
563 hydro_utilities::VectorXYZ<Real> pos_indU = Calc_Pos_IndU(i, particle_props, spatial_props);
564
565 if (checkDontSkip_and_maybeRevisePos(i, pos_indU)) {
566 bool is_scheduled = ov_scheduler.Is_Scheduled_And_Update(fb_model, particle_props.id_dev[i], pos_indU,
567 spatial_props.nx_g, spatial_props.ny_g);
568
569 if (is_scheduled) {
570 // note age_dev is actually the time of birth
571 const Real age = cycle_props.t - particle_props.age_dev[i];
572
573 // holds a reference to the particle's mass (this will be updated after feedback is handled)
574 Real& mass_ref = particle_props.mass_dev[i];
575
576#if FEEDBACK_LOG_INDIVIDUAL
577 // explicitly use json formatting to make log-parsing easier:
578 kernel_printf(
579 "...feedback-log-individual:\n"
580 " {\"block\": %d, \"thread\":%d, \"cycle\":%d,\n"
581 " \"index\": %d, \"id\": %lld, \"age\": %g,\n"
582 " \"mass (pre-feedback)\": %g, num_SN: %d\n"
583 " \"position (code units)\": [%g, %g, %g],\n"
584 " \"position (index-units)\": [%g, %g, %g],\n"
585 " \"vel (code-units)\": [%g, %g, %g]}\n",
586 blockIdx.x, threadIdx.x, cycle_props.n_step, i, (long long int)(particle_props.id_dev[i]), age, mass_ref,
587 num_SN_dev[i], particle_props.pos_x_dev[i], particle_props.pos_y_dev[i], particle_props.pos_z_dev[i],
588 pos_indU[0], pos_indU[1], pos_indU[2], particle_props.vel_x_dev[i], particle_props.vel_y_dev[i],
589 particle_props.vel_z_dev[i]);
590 int pre_countResolved = s_info[FBInfoLUT::countResolved];
591#endif /* FEEDBACK_LOG_INDIVIDUAL */
592
593 fb_model.apply_feedback(pos_indU[0], pos_indU[1], pos_indU[2], particle_props.vel_x_dev[i],
594 particle_props.vel_y_dev[i], particle_props.vel_z_dev[i], age, mass_ref,
595 particle_props.id_dev[i], spatial_props.dx, spatial_props.dy, spatial_props.dz,
596 spatial_props.nx_g, spatial_props.ny_g, spatial_props.nz_g, spatial_props.n_ghost,
597 num_SN_dev[i], cycle_props.n_step, s_info, conserved_dev);
598
599#if FEEDBACK_LOG_INDIVIDUAL
600 // explicitly use json formatting to make log-parsing easier:
601 kernel_printf(
602 "...feedback-log-individual-extra: {\"block\": %d, \"thread\":%d, \"cycle\":%d, \"id\": %lld, "
603 "\"isResolved\": %d}\n",
604 blockIdx.x, threadIdx.x, cycle_props.n_step, (long long int)(particle_props.id_dev[i]),
605 int(s_info[FBInfoLUT::countResolved] > pre_countResolved));
606#endif
607 }
608 }
609 }
610 }
611
612 // epilogue: sum the info from all threads (in all blocks) and add it into info
613 __syncthreads(); // synchronize all threads in the current block. It's important to do this before
614 // the next function call because we accumulate values on the local block first
615 reduction_utilities::blockAccumulateIntoNReals<FBInfoLUT::LEN, TPB_FEEDBACK>(info, s_info);
616}
617
619 void* kernel_ptr;
620 dim3 dim_block;
621 dim3 dim_grid;
622};
623
624/* Helper function that is used to fetch the appropriant feedback-kernel varient and compute the launch parameters
625 *
626 * The launch parameters are chosen in order to maximize parallelism; they are based on how many blocks can fit
627 * simultaneously on a SM (streaming multiprocessor), given the specified variant of the kernel, the number of
628 * threads per block, and the intended, per-block, shared dynamic memory usage.
629 *
630 * \note
631 * This has been factored out of Exec_Cluster_Feedback_Kernel() to allow \c BdryStrat to be specified as a runtime
632 * argument, while only having a single switch statement responsible for mapping the runtime argument to a template
633 * parameter.
634 */
635template <typename FeedbackModel, BoundaryStrategy BdryStrat>
636KernelAndLaunchConf fetch_kernel_and_launch_conf_(int threads_per_block, int max_num_threadblocks,
637 std::size_t dynamic_shared_mem_per_block)
638{
639 // do some work to configure the grid-size (i.e. the number of thread-blocks per grid)
640 // - since the kernel uses grid-wide synchronizations, some care needs to be taken to ensure
641 // co-residency of the thread blocks on the GPU (if you have too many thread-blocks, then they
642 // won't all be executed on the gpu at the same time)
643 // - we use static variables so we don't have to repeat these calculations. This should be fine as
644 // long as: - the amount of dynamic shared memory usage for each block never changes between calls
645 // - the threads per block don't ever change between calls
646 const dim3 dimBlock(threads_per_block, 1, 1);
647
648 static dim3 dimGrid;
649 static int last_max_num_threadblocks = 0;
650 static int last_threads_per_block = 0;
651 static std::size_t last_dynamic_shared_mem_per_block = 0;
652
653 CHOLLA_ASSERT(max_num_threadblocks > 0, "max_num_threadblocks must be positive!");
654 if ((last_max_num_threadblocks != max_num_threadblocks) or (last_threads_per_block != threads_per_block) or
655 (last_dynamic_shared_mem_per_block != dynamic_shared_mem_per_block)) {
656 last_max_num_threadblocks = max_num_threadblocks;
657 last_threads_per_block = threads_per_block;
658 last_dynamic_shared_mem_per_block = dynamic_shared_mem_per_block;
659
660 int dev = 0;
661 int supportsCoopLaunch = 0;
662 cudaError err = cudaDeviceGetAttribute(&supportsCoopLaunch, cudaDevAttrCooperativeLaunch, dev);
663 CHOLLA_ASSERT(cudaSuccess == err,
664 "Error encountered within cudaDeviceGetAttribute while querying whether the "
665 "system supports cooperative kernels");
666 CHOLLA_ASSERT(supportsCoopLaunch != 0, "System is unable to launch cooperative kernels");
667
668 cudaDeviceProp deviceProp;
669 cudaGetDeviceProperties(&deviceProp, dev);
670 int numBlocksPerSm = 0; // this will be updated to hold the max number of blocks on the GPU
671 err = cudaOccupancyMaxActiveBlocksPerMultiprocessor(&numBlocksPerSm,
672 Cluster_Feedback_Kernel<FeedbackModel, BdryStrat>,
673 threads_per_block, dynamic_shared_mem_per_block);
674 CHOLLA_ASSERT(cudaSuccess == err,
675 "Error encountered within cudaOccupancyMaxActiveBlocksPerMultiprocessor while "
676 "querying whether the max active blocks per SM");
677 CHOLLA_ASSERT(numBlocksPerSm > 0, "Something is wrong! The number of blocks per SM should be positive");
678
679 dimGrid = dim3(std::min(deviceProp.multiProcessorCount * numBlocksPerSm, max_num_threadblocks), 1, 1);
680 }
681
682 return KernelAndLaunchConf{(void*)Cluster_Feedback_Kernel<FeedbackModel, BdryStrat>, dimBlock, dimGrid};
683}
684
685/* Launches the Kernel for ClusterFeedback
686 *
687 * \tparam FeedbackModel type that encapsulates the actual feedback prescription
688 *
689 * \param[in,out] particle_props Encodes the actual particle data needed for feedback. If there is any feedback, the
690 * relevant particle properties (like particle mass) will be updated during this call.
691 * \param[in] spatial_props Encodes spatial information about the local domain and the fields
692 * \param[in] cycle_props Encodes details about the simulation's current (global) iteration cycle
693 * \param[out] info An array on the host that will is intended to accumulate summary details about the feedback
694 * during the course of this kernel call. This function assumes it has FBInfoLUT::LEN entries that are all initialized
695 * to 0. \param[out] conserved_dev pointer to the fluid-fields that will be updated during this function call.
696 * \param[in] An array of ``particle_props.n_local`` non-negative integers that specify the number of supernovae
697 * that are are scheduled to occur during the current cycle (for each particle). \param[in] ov_scheduler helps
698 * schedule feedback of particles with overlapping stencils. \param[in] bdry_strat specifies the policy used for
699 * handling feedback stencils that overlap with boundaries of the active zone \param[in] max_num_threadblocks This
700 * is here to put an arbitrary upper limit on the maximum number of thread-blocks. This is for debugging purposes. Only
701 * positive values are allowed.
702 */
703template <typename FeedbackModel>
704void Exec_Cluster_Feedback_Kernel(const feedback_details::ParticleProps& particle_props,
705 const feedback_details::FieldSpatialProps& spatial_props,
706 const feedback_details::CycleProps& cycle_props, Real* info, Real* conserved_dev,
707 int* num_SN_dev, OverlapScheduler& ov_scheduler, BoundaryStrategy bdry_strat,
708 int max_num_threadblocks = INT_MAX)
709{
710 // Declare/allocate device buffer for accumulating summary information about feedback
711 cuda_utilities::DeviceVector<Real> d_info(FBInfoLUT::LEN, true); // initialized to 0
712
713 // fetch the kernel and launch parameters (some care is taken to ensure that )
714 const std::size_t dynamic_shared_mem_per_block = 0;
715 const int threads_per_block = TPB_FEEDBACK;
716
717 KernelAndLaunchConf tmp;
718 switch (bdry_strat) {
719 case BoundaryStrategy::excludeGhostParticle_ignoreStencilIssues:
720 tmp = fetch_kernel_and_launch_conf_<FeedbackModel, BoundaryStrategy::excludeGhostParticle_ignoreStencilIssues>(
721 threads_per_block, max_num_threadblocks, dynamic_shared_mem_per_block);
722 break;
723 case BoundaryStrategy::excludeGhostParticle_snapActiveStencil:
724 tmp = fetch_kernel_and_launch_conf_<FeedbackModel, BoundaryStrategy::excludeGhostParticle_snapActiveStencil>(
725 threads_per_block, max_num_threadblocks, dynamic_shared_mem_per_block);
726 break;
727 default:
728 CHOLLA_ERROR(
729 "Unable to handle specified bdry_strat. This probably means a new stategy "
730 "was introduced without modifying the switch-statement this error occurs in.");
731 }
732
733 // actually launch the kernel
734 Real* d_info_ptr = d_info.data();
735 void* kernelArgs[] = {(void*)(&particle_props), (void*)(&spatial_props), (void*)(&cycle_props), (void*)(&d_info_ptr),
736 (void*)(&conserved_dev), (void*)(&num_SN_dev), (void*)(&ov_scheduler)};
737
738 cudaLaunchCooperativeKernel((void*)tmp.kernel_ptr, tmp.dim_grid, tmp.dim_block, kernelArgs,
739 dynamic_shared_mem_per_block, 0);
740
741 /*
742 // compute the grid-size or the number of thread-blocks per grid. The number of threads in a block is
743 // given by TPB_FEEDBACK
744 const int blocks_per_grid = (particle_props.n_local - 1) / TPB_FEEDBACK + 1;
745 hipLaunchKernelGGL(feedback_details::Cluster_Feedback_Kernel, blocks_per_grid, TPB_FEEDBACK, 0, 0,
746 particle_props, spatial_props, cycle_props, d_info.data(), conserved_dev, num_SN_dev,
747 feedback_model);
748 */
749
750 if (info != nullptr) {
751 // copy summary data back to the host
752 GPU_Error_Check(cudaMemcpy(info, d_info.data(), FBInfoLUT::LEN * sizeof(Real), cudaMemcpyDeviceToHost));
753 } else {
754 GPU_Error_Check(cudaDeviceSynchronize());
755 }
756}
757
758} // namespace feedback_details
A templatized class to encapsulate a device global memory pointer in a std::vector like interface com...
Definition DeviceVector.h:45
Definition kernel.h:244
Definition kernel.h:66
Real t
Definition kernel.h:67
int n_step
Definition kernel.h:69
Real dt
Definition kernel.h:68
Definition kernel.h:35
A data only struct that acts as a simple 3 element vector.
Definition basic_structs.h:32