DPNP C++ backend kernel library 0.21.0dev6
Data Parallel Extension for NumPy*
Loading...
Searching...
No Matches
putmask.hpp
1//*****************************************************************************
2// Copyright (c) 2026, Intel Corporation
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are met:
7// - Redistributions of source code must retain the above copyright notice,
8// this list of conditions and the following disclaimer.
9// - Redistributions in binary form must reproduce the above copyright notice,
10// this list of conditions and the following disclaimer in the documentation
11// and/or other materials provided with the distribution.
12// - Neither the name of the copyright holder nor the names of its contributors
13// may be used to endorse or promote products derived from this software
14// without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
26// THE POSSIBILITY OF SUCH DAMAGE.
27//*****************************************************************************
28
29#pragma once
30
31#include <cstddef>
32#include <cstdint>
33#include <vector>
34
35#include <sycl/sycl.hpp>
36
37// dpnp tensor headers
38#include "kernels/alignment.hpp"
39#include "kernels/dpnp_tensor_types.hpp"
40#include "utils/offset_utils.hpp"
41#include "utils/sycl_utils.hpp"
42#include "utils/type_utils.hpp"
43
44namespace dpnp::kernels::putmask
45{
46using dpnp::tensor::ssize_t;
47
48template <typename T, typename TwoOffsets_IndexerT>
50{
51private:
52 T *dst_ = nullptr;
53 const std::uint8_t *mask_u8_ = nullptr;
54 const T *vals_ = nullptr;
55 const TwoOffsets_IndexerT indexer_;
56 const std::size_t values_size_ = 0;
57
58public:
60 const bool *mask,
61 const T *vals,
62 const TwoOffsets_IndexerT &indexer,
63 const std::size_t values_size)
64 : dst_(dst), mask_u8_(reinterpret_cast<const std::uint8_t *>(mask)),
65 vals_(vals), indexer_(indexer), values_size_(values_size)
66 {
67 }
68
69 void operator()(sycl::id<1> wid) const
70 {
71 const std::size_t lin = wid[0];
72 auto offset = indexer_(static_cast<ssize_t>(lin));
73
74 const dpnp::tensor::ssize_t dst_off = offset.get_first_offset();
75 const dpnp::tensor::ssize_t mask_off = offset.get_second_offset();
76
77 if (mask_u8_[mask_off]) {
78 const std::size_t vlin = lin % values_size_;
79 dst_[dst_off] = vals_[vlin];
80 }
81 }
82};
83
84template <typename T,
85 std::uint8_t vec_sz = 4u,
86 std::uint8_t n_vecs = 2u,
87 bool enable_sg_loadstore = true>
89{
90private:
91 T *dst_ = nullptr;
92 const std::uint8_t *mask_u8_ = nullptr;
93 const T *values_ = nullptr;
94 std::size_t nelems_ = 0;
95 std::size_t val_size_ = 0;
96
97public:
99 const bool *mask,
100 const T *values,
101 const std::size_t nelems,
102 const std::size_t val_size)
103 : dst_(dst), mask_u8_(reinterpret_cast<const std::uint8_t *>(mask)),
104 values_(values), nelems_(nelems), val_size_(val_size)
105 {
106 }
107
108 void operator()(sycl::nd_item<1> ndit) const
109 {
110 const bool values_no_repeat = (val_size_ >= nelems_);
111
112 constexpr std::uint8_t elems_per_wi = n_vecs * vec_sz;
113 /* Each work-item processes vec_sz elements, contiguous in memory */
114 /* NOTE: work-group size must be divisible by sub-group size */
115
116 using dpnp::tensor::type_utils::is_complex_v;
117 if constexpr (enable_sg_loadstore && !is_complex_v<T>) {
118 auto sg = ndit.get_sub_group();
119 const std::uint32_t sgSize = sg.get_max_local_range()[0];
120 const std::size_t lane_id = sg.get_local_id()[0];
121
122 const std::size_t base =
123 elems_per_wi * (ndit.get_group(0) * ndit.get_local_range(0) +
124 sg.get_group_id()[0] * sgSize);
125
126 if (base + elems_per_wi * sgSize <= nelems_) {
127 using dpnp::tensor::sycl_utils::sub_group_load;
128 using dpnp::tensor::sycl_utils::sub_group_store;
129
130#pragma unroll
131 for (std::uint8_t it = 0; it < elems_per_wi; it += vec_sz) {
132 const std::size_t offset = base + it * sgSize;
133
134 auto dst_multi_ptr = sycl::address_space_cast<
135 sycl::access::address_space::global_space,
136 sycl::access::decorated::yes>(&dst_[offset]);
137 auto mask_multi_ptr = sycl::address_space_cast<
138 sycl::access::address_space::global_space,
139 sycl::access::decorated::yes>(&mask_u8_[offset]);
140
141 const sycl::vec<T, vec_sz> dst_vec =
142 sub_group_load<vec_sz>(sg, dst_multi_ptr);
143 const sycl::vec<std::uint8_t, vec_sz> mask_vec =
144 sub_group_load<vec_sz>(sg, mask_multi_ptr);
145
146 sycl::vec<T, vec_sz> val_vec;
147
148 if (values_no_repeat) {
149 auto values_multi_ptr = sycl::address_space_cast<
150 sycl::access::address_space::global_space,
151 sycl::access::decorated::yes>(&values_[offset]);
152
153 val_vec = sub_group_load<vec_sz>(sg, values_multi_ptr);
154 }
155 else {
156 const std::size_t idx = offset + lane_id;
157#pragma unroll
158 for (std::uint8_t k = 0; k < vec_sz; ++k) {
159 const std::size_t g =
160 idx + static_cast<std::size_t>(k) * sgSize;
161 val_vec[k] = values_[g % val_size_];
162 }
163 }
164
165 sycl::vec<T, vec_sz> out_vec;
166#pragma unroll
167 for (std::uint8_t vec_id = 0; vec_id < vec_sz; ++vec_id) {
168 out_vec[vec_id] = (mask_vec[vec_id]) ? val_vec[vec_id]
169 : dst_vec[vec_id];
170 }
171
172 sub_group_store<vec_sz>(sg, out_vec, dst_multi_ptr);
173 }
174 }
175 else {
176 const std::size_t lane_id = sg.get_local_id()[0];
177 for (std::size_t k = base + lane_id; k < nelems_; k += sgSize) {
178 if (mask_u8_[k]) {
179 const std::size_t v =
180 values_no_repeat ? k : (k % val_size_);
181 dst_[k] = values_[v];
182 }
183 }
184 }
185 }
186 else {
187 const std::size_t gid = ndit.get_global_linear_id();
188 const std::size_t gws = ndit.get_global_range(0);
189
190 for (std::size_t offset = gid; offset < nelems_; offset += gws) {
191 if (mask_u8_[offset]) {
192 const std::size_t v =
193 values_no_repeat ? offset : (offset % val_size_);
194 dst_[offset] = values_[v];
195 }
196 }
197 }
198 }
199};
200
201template <typename T>
202sycl::event putmask_strided_impl(sycl::queue &exec_q,
203 const int nd,
204 std::size_t nelems,
205 const dpnp::tensor::ssize_t *shape_strides,
206 char *dst_cp,
207 const dpnp::tensor::ssize_t dst_offset,
208 const char *mask_cp,
209 const dpnp::tensor::ssize_t mask_offset,
210 const char *values_cp,
211 std::size_t values_size,
212 const std::vector<sycl::event> &depends = {})
213{
214 dpnp::tensor::type_utils::validate_type_for_device<T>(exec_q);
215
216 T *dst_tp = reinterpret_cast<T *>(dst_cp);
217 const bool *mask_tp = reinterpret_cast<const bool *>(mask_cp);
218 const T *vals_tp = reinterpret_cast<const T *>(values_cp);
219
220 using IndexerT = dpnp::tensor::offset_utils::TwoOffsets_StridedIndexer;
221 const IndexerT indexer{nd, dst_offset, mask_offset, shape_strides};
222
223 return exec_q.submit([&](sycl::handler &cgh) {
224 cgh.depends_on(depends);
225
226 using PutMaskFunc = PutMaskStridedFunctor<T, IndexerT>;
227 cgh.parallel_for<PutMaskFunc>(
228 sycl::range<1>(nelems),
229 PutMaskFunc(dst_tp, mask_tp, vals_tp, indexer, values_size));
230 });
231}
232
233template <typename T, std::uint8_t vec_sz = 4u, std::uint8_t n_vecs = 2u>
234sycl::event putmask_contig_impl(sycl::queue &exec_q,
235 std::size_t nelems,
236 char *dst_cp,
237 const char *mask_cp,
238 const char *values_cp,
239 std::size_t values_size,
240 const std::vector<sycl::event> &depends = {})
241{
242 T *dst_tp = reinterpret_cast<T *>(dst_cp);
243 const bool *mask_tp = reinterpret_cast<const bool *>(mask_cp);
244 const T *values_tp = reinterpret_cast<const T *>(values_cp);
245
246 constexpr std::uint8_t elems_per_wi = n_vecs * vec_sz;
247 const std::size_t n_work_items_needed = nelems / elems_per_wi;
248 const std::size_t empirical_threshold = std::size_t(1) << 21;
249 const std::size_t lws = (n_work_items_needed <= empirical_threshold)
250 ? std::size_t(128)
251 : std::size_t(256);
252
253 const std::size_t n_groups =
254 ((nelems + lws * elems_per_wi - 1) / (lws * elems_per_wi));
255 const auto gws_range = sycl::range<1>(n_groups * lws);
256 const auto lws_range = sycl::range<1>(lws);
257
258 using dpnp::tensor::kernels::alignment_utils::is_aligned;
259 using dpnp::tensor::kernels::alignment_utils::required_alignment;
260
261 const bool aligned = is_aligned<required_alignment>(dst_tp) &&
262 is_aligned<required_alignment>(mask_tp) &&
263 is_aligned<required_alignment>(values_tp);
264
265 sycl::event comp_ev = exec_q.submit([&](sycl::handler &cgh) {
266 cgh.depends_on(depends);
267
268 if (aligned) {
269 constexpr bool enable_sg = true;
270 using PutMaskFunc =
271 PutMaskContigFunctor<T, vec_sz, n_vecs, enable_sg>;
272
273 cgh.parallel_for<PutMaskFunc>(
274 sycl::nd_range<1>(gws_range, lws_range),
275 PutMaskFunc(dst_tp, mask_tp, values_tp, nelems, values_size));
276 }
277 else {
278 constexpr bool enable_sg = false;
279 using PutMaskFunc =
280 PutMaskContigFunctor<T, vec_sz, n_vecs, enable_sg>;
281
282 cgh.parallel_for<PutMaskFunc>(
283 sycl::nd_range<1>(gws_range, lws_range),
284 PutMaskFunc(dst_tp, mask_tp, values_tp, nelems, values_size));
285 }
286 });
287
288 return comp_ev;
289}
290
291} // namespace dpnp::kernels::putmask