Skip to content

Commit c3ccc6c

Browse files
committed
[WIP] Histogram CPU
Input shape part. Signed-off-by: Piotr Rak <prak@nvidia.com>
1 parent 8a948ee commit c3ccc6c

5 files changed

Lines changed: 340 additions & 36 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) 2020-2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
16+
#ifndef DALI_OPERATORS_GENERIC_REDUCE_AXIS_HELPER_H__
17+
#define DALI_OPERATORS_GENERIC_REDUCE_AXIS_HELPER_H__
18+
19+
#include <vector>
20+
21+
#include "dali/pipeline/operator/operator.h"
22+
23+
namespace dali {
24+
namespace detail {
25+
26+
class AxesHelper {
27+
public:
28+
explicit inline AxesHelper(const OpSpec &spec) {
29+
has_axes_arg_ = spec.TryGetRepeatedArgument(axes_, "axes");
30+
has_axis_names_arg_ = spec.TryGetArgument(axis_names_, "axis_names");
31+
has_empty_axes_arg_ =
32+
(has_axes_arg_ && axes_.empty()) || (has_axis_names_arg_ && axis_names_.empty());
33+
34+
DALI_ENFORCE(!has_axes_arg_ || !has_axis_names_arg_,
35+
"Arguments `axes` and `axis_names` are mutually exclusive");
36+
}
37+
38+
void PrepareAxes(const TensorLayout &layout, int sample_dim) {
39+
if (has_axis_names_arg_) {
40+
axes_ = GetDimIndices(layout, axis_names_).to_vector();
41+
return;
42+
}
43+
44+
if (!has_axes_arg_) {
45+
axes_.resize(sample_dim);
46+
std::iota(axes_.begin(), axes_.end(), 0);
47+
}
48+
}
49+
50+
bool has_axes_arg_;
51+
bool has_axis_names_arg_;
52+
bool has_empty_axes_arg_;
53+
std::vector<int> axes_;
54+
TensorLayout axis_names_;
55+
};
56+
57+
} // namespace detail
58+
} // namespace dali
59+
60+
#endif // DALI_OPERATORS_GENERIC_REDUCE_AXIS_HELPER_H__
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "dali/operators/generic/reduce/histogram.h"
16+
17+
using namespace dali::hist_detail;
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
// Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef DALI_OPERATORS_GENERIC_REDUCE_HISTOGRAM_H_
16+
#define DALI_OPERATORS_GENERIC_REDUCE_HISTOGRAM_H_
17+
18+
#include "dali/kernels/kernel_manager.h"
19+
#include "dali/operators/generic/reduce/axes_helper.h"
20+
#include "dali/pipeline/operator/operator.h"
21+
22+
namespace dali {
23+
namespace hist_detail {
24+
25+
inline bool is_simple_reduction(const std::vector<int> &reduction_axes, int ndims,
26+
int hist_dim = 1) {
27+
int dim = ndims - hist_dim;
28+
29+
// Starting from inner dimension, look if we should reduce this axis
30+
// If we can do so until we can collapse next dimention.
31+
auto raxis = reduction_axes.rbegin();
32+
33+
for (; raxis != reduction_axes.rend(); ++raxis) {
34+
// TODO: Handle multidimentional histograms correctly
35+
if (dim != *raxis) {
36+
break;
37+
}
38+
--dim;
39+
}
40+
41+
// If there's no reduction axes left, we won't need transpose any axis.
42+
if (raxis == reduction_axes.rend())
43+
return true;
44+
45+
return false;
46+
}
47+
48+
// Collapses all inner dimensions
49+
std::vector<int> GetTransposeAxes(int ndims, const std::vector<int> &reduction_axes,
50+
int hist_dim = 1) {
51+
int dim = ndims - hist_dim;
52+
53+
// std::vector<int> dimensions_to_collapse;
54+
55+
// Starting from inner dimension, look if we should reduce this axis
56+
// We can collapse those dimension into one, that can be used to calculate histogram.
57+
auto raxis = reduction_axes.rbegin();
58+
59+
for (; raxis != reduction_axes.rend(); ++raxis, --dim) {
60+
if (dim != *raxis)
61+
break;
62+
// dimensions_to_collapse.insert(dimensions_to_collapse.begin(), dim);
63+
}
64+
65+
std::vector<int> axes_to_transpose;
66+
67+
// All axes that couldn't be collapsed, need to be transposed so we can collapse those together.
68+
int naxes_to_transpose = std::distance(raxis, reduction_axes.rend());
69+
70+
axes_to_transpose.resize(naxes_to_transpose);
71+
72+
auto raxes_start = reduction_axes.begin() + naxes_to_transpose;
73+
std::copy(reduction_axes.begin(), raxes_start, axes_to_transpose.begin());
74+
75+
return axes_to_transpose;
76+
}
77+
78+
class TransposeForReduction {
79+
80+
};
81+
82+
} // namespace hist_detail
83+
} // namespace dali
84+
85+
86+
87+
namespace dali {
88+
89+
template <typename Backend>
90+
class Histogram : public Operator<Backend>, detail::AxesHelper {
91+
public:
92+
explicit inline Histogram(const OpSpec &spec)
93+
: Operator<Backend>(spec), detail::AxesHelper(spec) {}
94+
95+
bool CanInferOutputs() const override {
96+
return true;
97+
}
98+
99+
~Histogram() override = default;
100+
101+
void PrepareInputShapesForReduction(const TensorListShape<> &input_shapes, int hist_dim = 1) {
102+
std::vector<TensorShape<>> reduction_input_shapes;
103+
reduction_input_shapes.reserve(input_shapes.num_samples());
104+
105+
const int ndims = input_shapes.sample_dim();
106+
107+
// TODO: support higher dimentionality histograms (hist_dim)
108+
std::array<std::pair<int, int>, 1> reduced_inner_dim(
109+
{std::make_pair(ndims - axes_.size() - 1, ndims - 1)});
110+
111+
for (int i = 0; i < input_shapes.num_samples(); ++i) {
112+
// Collapse inner dimensions
113+
auto shape = collapse_dims(input_shapes.tensor_shape(i), reduced_inner_dim);
114+
reduction_input_shapes.push_back(std::move(shape));
115+
}
116+
117+
reduced_input_shapes_ = TensorListShape<>(reduction_input_shapes);
118+
}
119+
120+
// For all subsequent dimensions go through the list and find axes not being
121+
// reduced
122+
SmallVector<int, 6> GetNonReductionAxes(const int ndims) const {
123+
SmallVector<int, 6> non_reduction_axes;
124+
125+
int rax_search_start = 0;
126+
127+
for (int dim = 0; dim < ndims; ++dim) {
128+
bool found = false;
129+
int rax = rax_search_start;
130+
131+
while (rax < axes_.size() && axes_[rax] <= dim) {
132+
// Since axes_ are sorted, we know we can skip ahead searching
133+
// of next dimension to at least to next reduction axis
134+
rax_search_start = rax + 1;
135+
136+
if (axes_[rax] == dim) {
137+
found = true;
138+
break;
139+
}
140+
++rax;
141+
}
142+
143+
if (!found) {
144+
non_reduction_axes.push_back(dim);
145+
}
146+
}
147+
148+
return non_reduction_axes;
149+
}
150+
151+
void PrepareInputShapesForTranspose(const TensorListShape<> &input_shapes, int hist_dim = 1) {
152+
const int ndims = input_shapes.sample_dim();
153+
154+
auto shape_span
155+
auto non_rediction_axes = GetNonReductionAxes(ndims);
156+
157+
// Axes inner of that can be collapsed directly and don't need to have an order changed
158+
SmallVector<int, 6> inner_reduction_axes;
159+
160+
// Go through reduction axes in inner dimension order
161+
// We stop when we find first axis that doesn't need reduction;
162+
for (int rax = axes_.size() - 1, dim = ndims; rax >= 0; --rax, --dim) {
163+
if (axes_[rax] != dim) {
164+
break;
165+
}
166+
167+
inner_reduction_axes.insert(inner_reduction_axes.begin(), dim);
168+
}
169+
170+
auto axes_to_transpose = hist_detail::GetTransposeAxes(ndims, axes_);
171+
172+
SmallVector<int, 6> axes_order;
173+
axes_order.reserve(ndims);
174+
for (int axis : non_rediction_axes) {
175+
axes_order.push_back(axis);
176+
}
177+
178+
for (int axis : axes_to_transpose) {
179+
axes_order.push_back(axis);
180+
}
181+
182+
for (int axis : axes_to_transpose) {
183+
axes_order.push_back(axis);
184+
}
185+
186+
TensorListShape<> shapes;
187+
permute_dims(shapes, input_shapes, axes_order);
188+
189+
PrepareInputShapesForReduction(shapes, hist_dim);
190+
191+
transpose_axes_order_ = std::move(axes_order);
192+
}
193+
194+
void PreparingOutputShapes(int hist_ndim = 1) {
195+
auto GetNonReductionAxes(reduced_input_shapes_.sample_dim());
196+
197+
for (int i = 0; i < reduced_input_shapes_.num_samples(); ++i) {
198+
199+
}
200+
}
201+
202+
bool SetupImpl(std::vector<OutputDesc> &output_desc, const workspace_t<Backend> &ws) override {
203+
output_desc.resize(1);
204+
205+
auto &input = ws.template Input<Backend>(0);
206+
const size_t ndims = input.shape().sample_dim();
207+
208+
PrepareAxes(input.GetLayout(), ndims);
209+
210+
// Empty reduction axes were specified, histogram calculation becomes identity operation
211+
is_noop_ = (has_axes_arg_ || has_axis_names_arg_) && axes_.empty();
212+
if (is_noop_) {
213+
output_desc[0].type = input.type();
214+
output_desc[0].shape = input.shape();
215+
} else {
216+
if (!axes_.empty()) {
217+
// Ensure reduction axes are sorted in ascending order,
218+
// so we can check dimentions that can be collapsed
219+
std::sort(axes_.begin(), axes_.end());
220+
}
221+
222+
if (hist_detail::is_simple_reduction(axes_, ndims)) {
223+
PrepareInputShapesForReduction(input.shape());
224+
} else {
225+
PrepareInputShapesForTranspose(input.shape());
226+
needs_transpose_ = true;
227+
}
228+
}
229+
return true;
230+
}
231+
232+
void RunImpl(workspace_t<Backend> &ws) override {}
233+
234+
private:
235+
USE_OPERATOR_MEMBERS();
236+
bool keep_dims_;
237+
bool needs_transpose_ = false;
238+
bool is_noop_ = false;
239+
TensorListShape<> reduced_input_shapes_;
240+
SmallVector<int, 6> transpose_axes_order_;
241+
kernels::KernelManager kmgr_;
242+
TensorView<StorageGPU, const int, 1> params_num_bins_gpu_;
243+
};
244+
245+
} // namespace dali
246+
247+
248+
#endif // DALI_OPERATORS_GENERIC_REDUCE_HISTOGRAM_H_

dali/operators/generic/reduce/reduce.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ DALI_SCHEMA(reductions__Max)
9999
.NumOutput(1)
100100
.AddParent("ReduceBase");
101101

102+
DALI_SCHEMA(reductions__Histogram)
103+
.DocStr("Calculates histogram.")
104+
.NumInput(1, 3)
105+
.NumOutput(1)
106+
.AddArg("num_bins", "Number of bins", DALI_INT_VEC, true)
107+
.AddParent("ReduceBase");
108+
109+
110+
using HistogramCPU = Histogram<CPUBackend>;
111+
DALI_REGISTER_OPERATOR(reductions__Histogram, HistogramCPU, CPU)
112+
102113
using MeanCPU = MeanOp<kernels::MeanCPU, CPUBackend>;
103114
DALI_REGISTER_OPERATOR(reductions__Mean, MeanCPU, CPU);
104115

0 commit comments

Comments
 (0)