ONE - On-device Neural Engine
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
CircleStridedSlice.cpp
Go to the documentation of this file.
1/*
2 * Copyright (c) 2021 Samsung Electronics Co., Ltd. All Rights Reserved
3 * Copyright 2018 The TensorFlow Authors. All Rights Reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
18
19#include "Check.h"
20#include "CircleCloneNode.h"
22
23#include <luci/IR/CircleNode.h>
24#include <loco/IR/DataType.h>
25#include <loco/IR/NodeShape.h>
26#include <oops/InternalExn.h>
27
28#include <algorithm>
29#include <cmath>
30#include <cstdint>
31#include <limits>
32
33namespace luci
34{
35
37{
38 auto *cloned = _graph->nodes()->create<luci::CircleStridedSlice>();
39 {
40 cloned->begin_mask(node->begin_mask());
41 cloned->end_mask(node->end_mask());
42 cloned->ellipsis_mask(node->ellipsis_mask());
43 cloned->new_axis_mask(node->new_axis_mask());
44 cloned->shrink_axis_mask(node->shrink_axis_mask());
45 }
46 return cloned;
47}
48
49// code referenced from
50// https://github.com/tensorflow/tensorflow/blob/3f878cff5b698b82eea85db2b60d65a2e320850e/
51// tensorflow/lite/kernels/strided_slice.cc
52// tensorflow/lite/kernels/internal/strided_slice_logic.h
53namespace sinf
54{
55
56// This Op only supports 1-5D cases and since we use the reference 4D
57// implementation, the 1-3D tensors are mapped to 4D.
58const int kMaxDim = 5;
59
60const loco::DataType S32 = loco::DataType::S32;
61
63{
65 int32_t start_indices[kMaxDim] = {0};
67 int32_t stop_indices[kMaxDim] = {0};
68 int8_t strides_count = 0;
69 int32_t strides[kMaxDim] = {0};
70
71 int16_t begin_mask = 0;
72 int16_t ellipsis_mask = 0;
73 int16_t end_mask = 0;
74 int16_t new_axis_mask = 0;
75 int16_t shrink_axis_mask = 0;
76};
77
79{
81 {
82 // check overflow issues
83 assert(static_cast<int16_t>(node->begin_mask()) == node->begin_mask());
84 assert(static_cast<int16_t>(node->ellipsis_mask()) == node->ellipsis_mask());
85 assert(static_cast<int16_t>(node->end_mask()) == node->end_mask());
86 assert(static_cast<int16_t>(node->new_axis_mask()) == node->new_axis_mask());
87 assert(static_cast<int16_t>(node->shrink_axis_mask()) == node->shrink_axis_mask());
88
91 params.end_mask = node->end_mask();
94
95 input = loco::must_cast<luci::CircleNode *>(node->input());
96 begin = loco::must_cast<luci::CircleConst *>(node->begin());
97 end = loco::must_cast<luci::CircleConst *>(node->end());
98 strides = loco::must_cast<luci::CircleConst *>(node->strides());
99
100 loco::TensorShape input_shape = circle_shape(input);
101 input_dims = static_cast<int64_t>(input_shape.rank());
102 }
108
109 // Equivalent input shape after adding axis according to new_axis_mask.
111 int64_t input_dims = 0;
112};
113
114// Use until std::clamp() is available from C++17.
115inline int Clamp(const int32_t v, const int32_t lo, const int32_t hi)
116{
117 LUCI_ASSERT(!(hi < lo), "Clamp hi < lo");
118 if (hi < v)
119 return hi;
120 if (v < lo)
121 return lo;
122 return v;
123}
124
125// Return the index for the first element along that axis. This index will be a
126// positive integer between [0, axis_size - 1] that can be used to index
127// directly into the data.
128inline int64_t StartForAxis(const StridedSliceParams &params, const loco::TensorShape &input_shape,
129 int64_t axis)
130{
131 const auto begin_mask = params.begin_mask;
132 const auto *start_indices = params.start_indices;
133 const auto *strides = params.strides;
134 const int64_t axis_size = static_cast<int64_t>(input_shape.dim(axis).value());
135 if (axis_size == 0)
136 {
137 return 0;
138 }
139 // Begin with the specified index.
140 int64_t start = start_indices[axis];
141
142 // begin_mask override
143 if (begin_mask & (1LL << axis))
144 {
145 if (strides[axis] > 0)
146 {
147 // Forward iteration - use the first element. These values will get
148 // clamped below (Note: We could have set them to 0 and axis_size-1, but
149 // use lowest() and max() to maintain symmetry with StopForAxis())
150 start = std::numeric_limits<int32_t>::lowest();
151 }
152 else
153 {
154 // Backward iteration - use the last element.
155 start = std::numeric_limits<int32_t>::max();
156 }
157 }
158
159 // Handle negative indices
160 if (start < 0)
161 {
162 start += axis_size;
163 }
164
165 // Clamping
166 if (strides[axis] > 0)
167 {
168 // Forward iteration
169 start = Clamp(start, 0, axis_size);
170 }
171 else
172 {
173 // Backward iteration
174 start = Clamp(start, -1, axis_size - 1);
175 }
176
177 return start;
178}
179
180// Return the "real" index for the end of iteration along that axis. This is an
181// "end" in the traditional C sense, in that it points to one past the last
182// element. ie. So if you were iterating through all elements of a 1D array of
183// size 4, this function would return 4 as the stop, because it is one past the
184// "real" indices of 0, 1, 2 & 3.
185inline int64_t StopForAxis(const StridedSliceParams &params, const loco::TensorShape &input_shape,
186 int64_t axis, int64_t start_for_axis)
187{
188 const auto end_mask = params.end_mask;
189 const auto shrink_axis_mask = params.shrink_axis_mask;
190 const auto *stop_indices = params.stop_indices;
191 const auto *strides = params.strides;
192 const int64_t axis_size = static_cast<int64_t>(input_shape.dim(axis).value());
193 if (axis_size == 0)
194 {
195 return 0;
196 }
197
198 // Begin with the specified index
199 const bool shrink_axis = shrink_axis_mask & (1LL << axis);
200 int64_t stop = stop_indices[axis];
201
202 // When shrinking an axis, the end position does not matter (and can be
203 // incorrect when negative indexing is used, see Issue #19260). Always use
204 // start_for_axis + 1 to generate a length 1 slice, since start_for_axis has
205 // already been adjusted for negative indices.
206 if (shrink_axis)
207 {
208 return start_for_axis + 1;
209 }
210
211 // end_mask override
212 if (end_mask & (1LL << axis))
213 {
214 if (strides[axis] > 0)
215 {
216 // Forward iteration - use the last element. These values will get
217 // clamped below
218 stop = std::numeric_limits<int32_t>::max();
219 }
220 else
221 {
222 // Backward iteration - use the first element.
223 stop = std::numeric_limits<int32_t>::lowest();
224 }
225 }
226
227 // Handle negative indices
228 if (stop < 0)
229 {
230 stop += axis_size;
231 }
232
233 // Clamping
234 // Because the end index points one past the last element, we need slightly
235 // different clamping ranges depending on the direction.
236 if (strides[axis] > 0)
237 {
238 // Forward iteration
239 stop = Clamp(stop, 0, axis_size);
240 }
241 else
242 {
243 // Backward iteration
244 stop = Clamp(stop, -1, axis_size - 1);
245 }
246
247 return stop;
248}
249
251{
252 StridedSliceParams op_params;
253
254 // The ellipsis_mask and new_axis_mask in op_params are not used. Those masks
255 // are processed here to update begin_mask, end_mask and the index range.
256 op_params.begin_mask = 0;
257 op_params.ellipsis_mask = 0;
258 op_params.end_mask = 0;
259 op_params.new_axis_mask = 0;
260 op_params.shrink_axis_mask = 0;
261
262 // Count indexes where the new_axis_mask is set but the ellipsis_mask is not.
263 loco::TensorShape begin_shape = circle_shape(op_context->begin);
264 const int64_t begin_count = static_cast<int64_t>(begin_shape.dim(0).value());
265 int64_t num_add_axis = 0;
266 for (int64_t i = 0; i < begin_count; ++i)
267 {
268 if (!((1LL << i) & op_context->params.ellipsis_mask) &&
269 ((1LL << i) & op_context->params.new_axis_mask))
270 {
271 num_add_axis++;
272 }
273 }
274
275 // Calculate the dims of input after adding new axises.
276 const int64_t effective_dims = op_context->input_dims + num_add_axis;
277
278 // If begin, end and strides are not fully provided, it means Ellipsis should
279 // be expanded to multiple dimensions (Ex: for spec [Ellipsis, 2] on a 3D
280 // input, the Ellipsis should be applied for the first 2 dimensions). Besides,
281 // If the new_axis_mask and the ellipsis_mask are set at the same index, the
282 // new_axis_mask will have no effect.
283 int64_t effective_ellipsis_mask = 0, effective_new_axis_mask = 0;
284 int64_t ellipsis_start_idx = effective_dims, expanded_ellipsis = 0;
285 for (int64_t i = 0; i < effective_dims;)
286 {
287 if ((1LL << i) & op_context->params.ellipsis_mask)
288 {
289 ellipsis_start_idx = i;
290 int64_t ellipsis_end_idx =
291 std::max(i + 1, std::min(i + 1 + num_add_axis + op_context->input_dims - begin_count,
292 effective_dims));
293 expanded_ellipsis = ellipsis_end_idx - ellipsis_start_idx - 1;
294
295 // Set bit for effective_ellipsis_mask.
296 for (; i < ellipsis_end_idx; ++i)
297 {
298 effective_ellipsis_mask |= (1LL << i);
299 }
300 continue;
301 }
302
303 if ((1LL << (i - expanded_ellipsis)) & op_context->params.new_axis_mask)
304 {
305 effective_new_axis_mask |= (1LL << i);
306 }
307 ++i;
308 }
309
310 // Calculate effective_input_shape and its corresponding begin, end, strides.
311 loco::TensorShape input_shape = circle_shape(op_context->input);
312 int64_t added_ellipsis = 0, added_axises = 0;
313 // make sure no overflow
314 assert(static_cast<uint32_t>(effective_dims) == effective_dims);
315 op_context->effective_input_shape.rank(effective_dims);
316
317 for (int64_t i = 0; i < effective_dims; ++i)
318 {
319 if ((1LL << i) & effective_ellipsis_mask)
320 {
321 // If ellipsis_mask, set the begin_mask and end_mask at that index.
322 added_ellipsis = std::max(int64_t(0), i - ellipsis_start_idx);
323 assert(i < 16);
324 op_params.begin_mask |= (1LL << i);
325 op_params.end_mask |= (1LL << i);
326 op_params.strides[i] = 1;
327 op_context->effective_input_shape.dim(i) = input_shape.dim(i - added_axises);
328 }
329 else if ((1LL << i) & effective_new_axis_mask)
330 {
331 // If new_axis_mask is set, it is equivalent to adding a new dim of 1 to
332 // input tensor. Store added shape to effective_input_shape.
333 op_params.start_indices[i] = 0;
334 op_params.stop_indices[i] = 1;
335 op_params.strides[i] = 1;
336 op_context->effective_input_shape.dim(i) = loco::Dimension(1);
337 added_axises++;
338 }
339 else if (i >= begin_count + expanded_ellipsis)
340 {
341 op_params.start_indices[i] = 0;
342 op_params.stop_indices[i] = 0;
343 op_params.strides[i] = 1;
344 assert(i < 16);
345 op_params.begin_mask |= (1LL << i);
346 op_params.end_mask |= (1LL << i);
347 op_context->effective_input_shape.dim(i) = input_shape.dim(i - added_axises);
348 }
349 else
350 {
351 const int64_t orig_idx = i - added_ellipsis;
352 op_params.start_indices[i] = op_context->begin->at<S32>(orig_idx);
353 op_params.stop_indices[i] = op_context->end->at<S32>(orig_idx);
354 op_params.strides[i] = op_context->strides->at<S32>(orig_idx);
355 if (op_context->params.begin_mask & (1LL << orig_idx))
356 {
357 assert(i < 16);
358 op_params.begin_mask |= (1LL << i);
359 }
360 if (op_context->params.end_mask & (1LL << orig_idx))
361 {
362 assert(i < 16);
363 op_params.end_mask |= (1LL << i);
364 }
365 if (op_context->params.shrink_axis_mask & (1LL << orig_idx))
366 {
367 assert(i < 16);
368 op_params.shrink_axis_mask |= (1LL << i);
369 }
370 op_context->effective_input_shape.dim(i) = input_shape.dim(i - added_axises);
371 }
372 }
373
374 // make sure no overflow
375 assert(static_cast<int8_t>(effective_dims) == static_cast<int32_t>(effective_dims));
376
377 op_params.start_indices_count = effective_dims;
378 op_params.stop_indices_count = effective_dims;
379 op_params.strides_count = effective_dims;
380
381 return op_params;
382}
383
385{
387
388 auto input_node = loco::must_cast<luci::CircleNode *>(node->input());
389
390 auto begin_node = loco::must_cast<luci::CircleNode *>(node->begin());
391 auto end_node = loco::must_cast<luci::CircleNode *>(node->end());
392 auto strides_node = loco::must_cast<luci::CircleNode *>(node->strides());
393
394 LUCI_ASSERT(begin_node->dtype() == S32, "Only support S32 for begin_node");
395 LUCI_ASSERT(end_node->dtype() == S32, "Only support S32 for end_node");
396 LUCI_ASSERT(strides_node->dtype() == S32, "Only support S32 for strides_node");
397
398 LUCI_ASSERT(begin_node->rank() == 1, "Only support rank 1 for begin_node");
399 LUCI_ASSERT(end_node->rank() == 1, "Only support rank 1 for end_node");
400 LUCI_ASSERT(strides_node->rank() == 1, "Only support rank 1 for strides_node");
401
402 auto begin_const = dynamic_cast<luci::CircleConst *>(node->begin());
403 auto end_const = dynamic_cast<luci::CircleConst *>(node->end());
404 auto strides_const = dynamic_cast<luci::CircleConst *>(node->strides());
405 // TODO support non-const strides_node
406 if (strides_const == nullptr)
407 {
408 INTERNAL_EXN("StridedSlice strides node is not Constant");
409 }
410 if (begin_const == nullptr || end_const == nullptr)
411 {
412 // The dimensions of the output shape are all set to unknown.
413 output_shape.rank(input_node->rank());
414 return output_shape;
415 }
416
418
419 assert(begin_const->size<S32>() <= input_shape.rank());
420 assert(end_const->size<S32>() <= input_shape.rank());
421 assert(strides_const->size<S32>() <= input_shape.rank());
422
423 StridedSliceContext op_context(node);
424 auto op_params = BuildStridedSliceParams(&op_context);
425 auto &effective_input_shape = op_context.effective_input_shape;
426 std::vector<int64_t> output_shape_vector;
427 std::vector<bool> output_known_vector;
428
429 for (int32_t idx = effective_input_shape.rank() - 1; idx >= 0; --idx)
430 {
431 int32_t stride = op_params.strides[idx];
432 LUCI_ASSERT(stride != 0, "stride value has to be non-zero");
433
434 int64_t begin = StartForAxis(op_params, effective_input_shape, idx);
435 int64_t end = StopForAxis(op_params, effective_input_shape, idx, begin);
436
437 // When shrinking an axis, the end position does not matter (and can be
438 // incorrect when negative indexing is used, see Issue #19260). Always use
439 // begin + 1 to generate a length 1 slice, since begin has
440 // already been adjusted for negative indices by GetBeginValueAtIndex.
441 const bool shrink_axis = op_params.shrink_axis_mask & (1 << idx);
442 if (shrink_axis)
443 {
444 end = begin + 1;
445 }
446
447 // This is valid for both positive and negative strides
448 int64_t dim_shape = std::ceil((end - begin) / static_cast<float>(stride));
449 dim_shape = dim_shape < 0 ? 0 : dim_shape;
450 if (!shrink_axis)
451 {
452 output_shape_vector.push_back(dim_shape);
453 output_known_vector.push_back(effective_input_shape.dim(idx).known());
454 }
455 }
456
457 auto shape_size = output_shape_vector.size();
458 output_shape.rank(shape_size);
459 for (uint32_t idx = 0; idx < shape_size; ++idx)
460 {
461 bool known = output_known_vector[shape_size - 1u - idx];
462 if (not known)
463 continue;
464 int64_t dim = output_shape_vector.at(shape_size - 1u - idx);
465 LUCI_ASSERT(0 <= dim && dim < 0xfffffffL, "Dimension size exceeds limit");
466 // reverse copy
467 output_shape.dim(idx) = static_cast<uint32_t>(dim);
468 }
469
470 return output_shape;
471}
472
473} // namespace sinf
474} // namespace luci
#define INTERNAL_EXN(msg)
@ brief throw internal exception with message
Definition InternalExn.h:25
The value of one dimension in a tensor shape.
Definition Dimension.h:30
uint32_t value(void) const
Return the value.
Definition Dimension.h:51
const Dimension & dim(uint32_t axis) const
Definition TensorShape.h:38
uint32_t rank(void) const
Definition TensorShape.h:35
Class to build tensor data.
Definition CircleConst.h:35
const loco::DataTypeImpl< DT >::Type & at(uint32_t n) const
STRIDED_SLICE in Circle.
loco::Node * input(void) const
loco::Node * begin(void) const
loco::Node * strides(void) const
loco::Node * end(void) const
loco::TensorShape visit(const luci::CircleNode *node) final
Default fallback.
const luci_interpreter::RuntimeShape output_shape
#define LUCI_ASSERT(condition, msg)
Definition Check.h:26
DataType
"scalar" value type
Definition DataType.h:27
int64_t StopForAxis(const StridedSliceParams &params, const loco::TensorShape &input_shape, int64_t axis, int64_t start_for_axis)
const loco::DataType S32
int Clamp(const int32_t v, const int32_t lo, const int32_t hi)
loco::TensorShape circle_shape(const luci::CircleNode *node)
int64_t StartForAxis(const StridedSliceParams &params, const loco::TensorShape &input_shape, int64_t axis)
StridedSliceParams BuildStridedSliceParams(StridedSliceContext *op_context)
CircleInput * input_node(loco::Graph *g, const loco::GraphInputIndex &index)
Find a Pull node with a given input index.
int8_t begin_count
Definition Slice.cpp:32
int32_t begin[5]
Definition Slice.cpp:33
StridedSliceContext(const luci::CircleStridedSlice *node)