Shamrock 2025.10.0
Astrophysical Code
Loading...
Searching...
No Matches
is_all_true.cpp
Go to the documentation of this file.
1// -------------------------------------------------------//
2//
3// SHAMROCK code for hydrodynamics
4// Copyright (c) 2021-2026 Timothée David--Cléris <tim.shamrock@proton.me>
5// SPDX-License-Identifier: CeCILL Free Software License Agreement v2.1
6// Shamrock is licensed under the CeCILL 2.1 License, see LICENSE for more information
7//
8// -------------------------------------------------------//
9
15
18#include "shambase/memory.hpp"
25
26namespace {
27
28 template<class T>
29 bool is_all_true_host(sham::DeviceBuffer<T> &buf, u32 cnt) {
30
31 {
32 auto tmp = buf.copy_to_stdvec();
33
34 for (u32 i = 0; i < cnt; i++) {
35 if (tmp[i] == 0) {
36 return false;
37 }
38 }
39 }
40
41 return true;
42 }
43
44 template<class T>
45 bool is_all_true_sum_reduction(sham::DeviceBuffer<T> &buf, u32 cnt) {
46
47 if (cnt == 0) {
48 return true;
49 }
50
51 auto dev_sched = buf.get_dev_scheduler_ptr();
52
53 sham::DeviceBuffer<u32> tmp(cnt, dev_sched);
54
56 shambase::get_check_ref(dev_sched).get_queue(),
57 sham::MultiRef{buf},
58 sham::MultiRef{tmp},
59 cnt,
60 [](u32 i, const T *in, u32 *out) {
61 out[i] = in[i] != 0;
62 });
63
64 auto count_true = shamalgs::primitives::sum(dev_sched, tmp, 0, cnt);
65
66 return count_true == cnt;
67 }
68
69 template<class T>
70 bool is_all_true_early_group_exit(sham::DeviceBuffer<T> &buf, u32 cnt, u32 group_size) {
71
72 if (cnt == 0) {
73 return true;
74 }
75
76 auto dev_sched = buf.get_dev_scheduler_ptr();
77 auto &q = dev_sched->get_queue();
78
79 sham::DeviceBuffer<u32> stop_flag(1, dev_sched);
80 stop_flag.fill(0);
81
82 /*
83 // To test to further optimize we can do something like:
84 // (i tried and only got +30% which good but less than i expected)
85 // A.K.A as something to do in another PR or never or I spam Claude on it
86
87 auto range = sham::make_ndrange(group_size, (cnt + 3) / 4);
88
89 <...>
90
91 // fetch the u8 4 by 4, complete with 0x01 (s) if idx + 4 > cnt
92 auto fetch_4i8 = [ptr = buf, cnt](u32 idx) -> u32 {
93 if (idx + 4 <= cnt)
94 return *reinterpret_cast<const u32 *>(ptr + idx);
95
96 u32 v = 0;
97 u32 i = 0;
98#pragma unroll
99 for (; idx + i < cnt; ++i)
100 v |= u32(ptr[idx + i]) << (i * 8);
101 if (i < 4)
102 v |= u32(0x01) << (i * 8);
103 return v;
104 };
105
106 u32 gid = item.get_global_linear_id();
107
108 // if there are
109 bool local = (gid < cnt) ? (fetch_4i8(gid * 4) == 0x01010101) : true;
110 */
111
112 // TODO: switch to the check version when available
113 auto range = sham::make_ndrange(group_size, cnt);
114
115 sham::kernel_call_hndl(
116 q,
117 sham::MultiRef{buf},
118 sham::MultiRef{stop_flag},
119 u32{1}, // TODO that when we have the new variant without it
120 [=](u32, const T *buf, u32 *stop) {
121 return [=](sycl::handler &cgh) {
122 cgh.parallel_for(range, [=](sycl::nd_item<1> item) {
123 auto grp = item.get_group();
124 u32 lid = item.get_local_linear_id();
125
126 // Only the group leader reads the stop flag from device memory,
127 // then broadcast that single value to the rest of the group instead
128 // of every work-item issuing its own global memory load.
129 u32 stop_val = sycl::group_broadcast(grp, (lid == 0) ? *stop : u32{0}, 0);
130
131 // early exit the whole group if the flag is set
132 if (stop_val) {
133 return;
134 }
135
136 u32 gid = item.get_global_linear_id();
137
138 bool local = (gid < cnt) ? (buf[gid] != 0) : true;
139
140 // reduce in lid==0 the sum of local
141 bool result = sycl::all_of_group(grp, local);
142
143 if (lid == 0) {
144 // if there is a false we set the stop flag
145 if (!result && !(*stop)) {
146 sycl::atomic_ref<
147 u32,
148 sycl::memory_order_relaxed,
149 sycl::memory_scope_device,
150 sycl::access::address_space::global_space>
151 atom(*stop);
152 atom |= 1_u32;
153 }
154 }
155 });
156 };
157 });
158
159 return stop_flag.get_val_at_idx(0) == 0;
160 }
161
162} // namespace
163
165
167 struct Host {
168 static constexpr std::string_view variant_type_name = "host";
169 };
170
173 static constexpr std::string_view variant_type_name = "sum_reduction";
174 };
175
178 static constexpr std::string_view variant_type_name = "atomic_early_exit";
179 u32 group_size = 256;
180
183 static std::vector<AtomicEarlyExit> variant_custom_defaults() {
184 return {AtomicEarlyExit{64}, AtomicEarlyExit{256}};
185 }
186 };
187} // namespace shamalgs::primitives::impl
188
189template<>
191 static nlohmann::json to_json(const shamalgs::primitives::impl::AtomicEarlyExit &p) {
192 return {{"group_size", p.group_size}};
193 }
194 static shamalgs::primitives::impl::AtomicEarlyExit from_json(const nlohmann::json &j) {
196 if (j.contains("group_size")) {
197 p.group_size = j.at("group_size").get<u32>();
198 }
199 return p;
200 }
201};
202
203namespace shamalgs::primitives {
204
206 namespace impl {
207
209 [](const sham::DeviceScheduler_ptr &, auto &self) {
210 self.set(Host{});
211 }};
212
214 std::vector<std::string> get_default_impl_list_is_all_true() {
215 return is_all_true_impl.get_default_config_list();
216 }
217
219 std::string get_current_impl_is_all_true() { return is_all_true_impl.get_current_config(); }
220
222 bool is_impl_set_is_all_true() { return is_all_true_impl.is_set(); }
223
225 void set_impl_is_all_true(const std::string &impl) {
226 shamlog_info_ln("algs", "setting is_all_true implementation to impl :", impl);
227 is_all_true_impl.set(impl);
228 }
229
231 void autoselect_impl_is_all_true(const sham::DeviceScheduler_ptr &dev_sched) {
232 is_all_true_impl.autoselect(dev_sched);
233 shamlog_info_ln(
234 "algs",
235 "defaulting is_all_true implementation to impl :",
237 }
238
239 } // namespace impl
240
241 template<class T>
243
244 if (!impl::is_all_true_impl.is_set()) {
246 }
247
248 return std::visit(
250 [&](impl::Host) {
251 return is_all_true_host(buf, cnt);
252 },
253 [&](impl::SumReduction) {
254 return is_all_true_sum_reduction(buf, cnt);
255 },
256 [&](impl::AtomicEarlyExit cfg) {
257 return is_all_true_early_group_exit(buf, cnt, cfg.group_size);
258 },
259 },
260 impl::is_all_true_impl.get());
261 }
262
263 template bool is_all_true(sham::DeviceBuffer<u8> &buf, u32 cnt);
264
265} // namespace shamalgs::primitives
266
267template<class T>
268bool shamalgs::primitives::is_all_true(sycl::buffer<T> &buf, u32 cnt) {
269
270 // TODO do it on GPU pleeeaze
271 {
272 sycl::host_accessor acc{buf, sycl::read_only};
273
274 for (u32 i = 0; i < cnt; i++) {
275 if (acc[i] == 0) {
276 return false;
277 }
278 }
279 }
280
281 return true;
282}
283
284template bool shamalgs::primitives::is_all_true(sycl::buffer<u8> &buf, u32 cnt);
Generic std::variant-based implementation selector.
std::uint32_t u32
32 bit unsigned integer
A buffer allocated in USM (Unified Shared Memory).
std::shared_ptr< DeviceScheduler > & get_dev_scheduler_ptr()
Gets the Device scheduler pointer corresponding to the held allocation.
std::vector< T > copy_to_stdvec() const
Copy the content of the buffer to a std::vector.
Drop-in replacement for the hand-rolled "global variable + enum + name mapping.
Boolean reduction algorithm for checking if all elements are non-zero.
sycl::nd_range< 1 > make_ndrange(u32 wg_size, u32 nthread)
void kernel_call(sham::DeviceQueue &q, RefIn in, RefOut in_out, u32 n, Functor &&func, SourceLocation &&callsite=SourceLocation{})
Submit a kernel to a SYCL queue.
namespace to control implementation behavior
bool is_impl_set_is_all_true()
Check if an implementation has been selected for is_all_true.
void autoselect_impl_is_all_true(const sham::DeviceScheduler_ptr &dev_sched)
Select the default implementation for is_all_true.
void set_impl_is_all_true(const std::string &impl)
Set the implementation for is_all_true, from a config json string.
std::string get_current_impl_is_all_true()
Get the current implementation for is_all_true, as a config json string.
std::vector< std::string > get_default_impl_list_is_all_true()
Get list of available is_all_true implementations, as config json strings.
namespace for primitive algorithm (e.g. sort, scan, reductions, ...)
T sum(const sham::DeviceScheduler_ptr &sched, const sham::DeviceBuffer< T > &buf1, u32 start_id, u32 end_id)
Compute the sum of elements in a device buffer within a specified range.
bool is_all_true(sycl::buffer< T > &buf, u32 cnt)
Check if all elements in a sycl::buffer are non-zero.
namespace to contain everything implemented by shamalgs
Definition algorithm.hpp:21
T & get_check_ref(const std::unique_ptr< T > &ptr, SourceLocation loc=SourceLocation())
Takes a std::unique_ptr and returns a reference to the object it holds. It throws a std::runtime_erro...
Definition memory.hpp:112
A class that references multiple buffers or similar objects.
Definition MultiRef.hpp:33
Customization point controlling how an alternative's fields (if any) are serialized to / parsed from ...
static Alt from_json(const nlohmann::json &)
Parse the alternative's fields back (default: no fields, ignored).
static nlohmann::json to_json(const Alt &)
Serialize the alternative's fields (default: no fields, empty object).
Check all elements via a sum reduction on device.
static std::vector< AtomicEarlyExit > variant_custom_defaults()
Check all elements on host after copying the buffer back.
Check all elements via a sum reduction on device.
Build an overload set out of several callables, for use with std::visit.