-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add SegmentMax-16
reference implementation
#28788
base: master
Are you sure you want to change the base?
Changes from all commits
d338b7e
462c43f
e3e44e2
adcce7d
1affe5d
1246f0a
b3a13d3
094131d
f8c9919
f841c4c
3e882ed
7cef3b5
36e4bf6
85bb1e6
53c02aa
5ff3e4f
3415e83
fb76824
b8645d3
da74ce4
4aed4e9
2374c1b
8c86ddf
b1c4ca4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// Copyright (C) 2018-2025 Intel Corporation | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
#pragma once | ||
|
||
#include "openvino/op/op.hpp" | ||
|
||
namespace ov { | ||
namespace op { | ||
namespace v16 { | ||
/// \brief An operation which computes the maximum values along segments of a tensor. | ||
/// \ingroup ov_ops_cpp_api | ||
class OPENVINO_API SegmentMax : public ov::op::Op { | ||
public: | ||
OPENVINO_OP("SegmentMax", "opset16", ov::op::Op); | ||
|
||
SegmentMax() = default; | ||
|
||
/// \brief Constructs a SegmentMax operation. | ||
/// | ||
/// \param data Input tensor with data | ||
/// \param segment_ids Indices of segments in the data input tensor | ||
/// \param fill_mode The value assigned to segments which are empty | ||
SegmentMax(const Output<Node>& data, const Output<Node>& segment_ids, const op::FillMode fill_mode); | ||
|
||
/// \brief Constructs a SegmentMax operation. | ||
/// | ||
/// \param data Input tensor with data | ||
/// \param segment_ids Indices of segments in the data input tensor | ||
/// \param num_segments The segments count | ||
/// \param fill_mode The value assigned to segments which are empty | ||
SegmentMax(const Output<Node>& data, | ||
const Output<Node>& segment_ids, | ||
const Output<Node>& num_segments, | ||
const op::FillMode fill_mode); | ||
|
||
bool visit_attributes(AttributeVisitor& visitor) override; | ||
void validate_and_infer_types() override; | ||
std::shared_ptr<Node> clone_with_new_inputs(const OutputVector& new_args) const override; | ||
|
||
const op::FillMode get_fill_mode() const; | ||
|
||
private: | ||
op::FillMode m_fill_mode; | ||
}; | ||
|
||
} // namespace v16 | ||
} // namespace op | ||
} // namespace ov |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,56 @@ | ||||||||
// Copyright (C) 2018-2025 Intel Corporation | ||||||||
// SPDX-License-Identifier: Apache-2.0 | ||||||||
// | ||||||||
|
||||||||
#pragma once | ||||||||
|
||||||||
#include <algorithm> | ||||||||
#include <limits> | ||||||||
#include <vector> | ||||||||
|
||||||||
#include "openvino/core/shape.hpp" | ||||||||
|
||||||||
namespace ov { | ||||||||
namespace reference { | ||||||||
|
||||||||
template <typename T, typename T_idx> | ||||||||
void segment_max(const T* data, | ||||||||
const Shape& data_shape, | ||||||||
const T_idx* segment_ids, | ||||||||
Comment on lines
+16
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To avoid additional type specialization for int32 and int64 (in favour of smaller binary size) there is a common approach to upcast the i32 to i64, and use only i64 in the ref instead of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An example here |
||||||||
T* out, | ||||||||
const Shape& output_shape, | ||||||||
const T empty_segment_value) { | ||||||||
const T_idx num_segments = output_shape[0]; | ||||||||
const size_t inner_dim_size = | ||||||||
std::accumulate(data_shape.begin() + 1, data_shape.end(), 1, std::multiplies<size_t>()); | ||||||||
Comment on lines
+24
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
std::vector<std::vector<T>> max_values(num_segments, | ||||||||
std::vector<T>(inner_dim_size, std::numeric_limits<T>::lowest())); | ||||||||
Comment on lines
+26
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can be input data copy avoided and set max values in the result tensor instead? |
||||||||
std::vector<bool> segment_has_values(num_segments, false); | ||||||||
|
||||||||
// Iterate over each element in the first dimension | ||||||||
T_idx base_idx; | ||||||||
for (size_t i = 0; i < data_shape[0]; ++i) { | ||||||||
const T_idx segment_id = segment_ids[i]; | ||||||||
if (segment_id >= num_segments) { | ||||||||
break; | ||||||||
} | ||||||||
segment_has_values[segment_id] = true; | ||||||||
// Iterate over each element in the inner dimensions | ||||||||
base_idx = i * inner_dim_size; | ||||||||
for (size_t j = 0; j < inner_dim_size; ++j) { | ||||||||
// Update the maximum value for the current segment and inner dimension | ||||||||
max_values[segment_id][j] = std::max(max_values[segment_id][j], data[base_idx + j]); | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
// Populate the output array with the maximum values for each segment | ||||||||
for (T_idx segment_id = 0; segment_id < num_segments; ++segment_id) { | ||||||||
base_idx = segment_id * inner_dim_size; | ||||||||
for (size_t j = 0; j < inner_dim_size; ++j) { | ||||||||
out[base_idx + j] = segment_has_values[segment_id] ? max_values[segment_id][j] : empty_segment_value; | ||||||||
} | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
} // namespace reference | ||||||||
} // namespace ov |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// Copyright (C) 2018-2025 Intel Corporation | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
#pragma once | ||
|
||
#include <cmath> | ||
|
||
#include "openvino/op/segment_max.hpp" | ||
#include "utils.hpp" | ||
|
||
namespace ov { | ||
namespace op { | ||
namespace v16 { | ||
template <class TShape, class TRShape = result_shape_t<TShape>> | ||
std::vector<TRShape> shape_infer(const SegmentMax* op, | ||
const std::vector<TShape>& input_shapes, | ||
const ITensorAccessor& tensor_accessor = make_tensor_accessor()) { | ||
NODE_VALIDATION_CHECK(op, input_shapes.size() == 2 || input_shapes.size() == 3); | ||
|
||
// validate data input | ||
const auto& data_shape = input_shapes[0]; | ||
const auto is_data_shape_rank_static = data_shape.rank().is_static(); | ||
if (is_data_shape_rank_static) { | ||
NODE_SHAPE_INFER_CHECK(op, input_shapes, data_shape.size() > 0, "The data input cannot be a scalar."); | ||
} | ||
|
||
// validate segment_ids input | ||
const auto& segment_ids_shape = input_shapes[1]; | ||
const auto is_segment_ids_rank_static = segment_ids_shape.rank().is_static(); | ||
if (is_segment_ids_rank_static) { | ||
NODE_SHAPE_INFER_CHECK(op, | ||
input_shapes, | ||
segment_ids_shape.size() == 1, | ||
"segment_ids must be a 1D input. Got: ", | ||
segment_ids_shape); | ||
if (is_data_shape_rank_static) { | ||
NODE_SHAPE_INFER_CHECK(op, | ||
input_shapes, | ||
data_shape[0].compatible(segment_ids_shape[0]), | ||
"The number of elements in segment_ids must match the first dimension of data."); | ||
} | ||
} | ||
const auto segment_ids = ov::op::get_input_const_data_as<TRShape, int64_t>(op, 1, tensor_accessor); | ||
if (segment_ids) { | ||
NODE_VALIDATION_CHECK(op, | ||
std::is_sorted(segment_ids->begin(), segment_ids->end()), | ||
"segment_ids must be sorted."); | ||
} | ||
|
||
// validate num_segments input | ||
const auto num_segments_available = op->inputs().size() == 3; | ||
const auto num_segments = num_segments_available ? get_input_const_data_as_shape<TRShape>(op, 2, tensor_accessor) | ||
: ov::optional<TRShape>{}; | ||
if (num_segments_available) { | ||
const auto& num_segments_shape = input_shapes[2]; | ||
NODE_SHAPE_INFER_CHECK(op, | ||
input_shapes, | ||
num_segments_shape.rank().compatible(0), | ||
"num_segments must be a scalar input. Got: ", | ||
num_segments_shape); | ||
} | ||
|
||
if (!is_data_shape_rank_static) { | ||
return {PartialShape::dynamic()}; | ||
} | ||
using TDim = typename TShape::value_type; | ||
auto output_shapes = std::vector<TRShape>{data_shape}; | ||
auto& output_shape = output_shapes[0]; | ||
if (num_segments) { | ||
output_shape[0] = TDim((*num_segments)[0]); | ||
} else if (segment_ids && | ||
op->inputs().size() == | ||
2) { // if num_segments is an input but not provided, the first dimension should still be dynamic | ||
output_shape[0] = TDim(*std::max_element(segment_ids->begin(), segment_ids->end()) + 1); | ||
} else { | ||
output_shape[0] = Dimension::dynamic(); | ||
} | ||
return output_shapes; | ||
} | ||
} // namespace v16 | ||
} // namespace op | ||
} // namespace ov |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// Copyright (C) 2018-2025 Intel Corporation | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
#include "openvino/op/segment_max.hpp" | ||
|
||
#include "itt.hpp" | ||
#include "openvino/core/validation_util.hpp" | ||
#include "openvino/op/op.hpp" | ||
#include "segment_max_shape_inference.hpp" | ||
|
||
namespace ov { | ||
namespace op { | ||
namespace v16 { | ||
|
||
SegmentMax::SegmentMax(const Output<Node>& data, const Output<Node>& segment_ids, const op::FillMode fill_mode) | ||
: Op({data, segment_ids}), | ||
m_fill_mode(fill_mode) { | ||
constructor_validate_and_infer_types(); | ||
} | ||
|
||
SegmentMax::SegmentMax(const Output<Node>& data, | ||
const Output<Node>& segment_ids, | ||
const Output<Node>& num_segments, | ||
const op::FillMode fill_mode) | ||
: Op({data, segment_ids, num_segments}), | ||
m_fill_mode(fill_mode) { | ||
constructor_validate_and_infer_types(); | ||
} | ||
|
||
bool SegmentMax::visit_attributes(ov::AttributeVisitor& visitor) { | ||
OV_OP_SCOPE(v16_SegmentMax_visit_attributes); | ||
visitor.on_attribute("fill_mode", m_fill_mode); | ||
return true; | ||
} | ||
|
||
void SegmentMax::validate_and_infer_types() { | ||
OV_OP_SCOPE(v16_SegmentMax_validate_and_infer_types); | ||
const auto& segment_ids_element_type = get_input_element_type(1); | ||
NODE_VALIDATION_CHECK(this, | ||
segment_ids_element_type == element::i32 || segment_ids_element_type == element::i64, | ||
"The element type of the segment_ids input be i32 or i64. Got: ", | ||
segment_ids_element_type); | ||
if (inputs().size() == 3) { | ||
const auto& num_segments_element_type = get_input_element_type(2); | ||
NODE_VALIDATION_CHECK(this, | ||
num_segments_element_type == element::i32 || num_segments_element_type == element::i64, | ||
"The element type of the num_segments input be i32 or i64. Got: ", | ||
num_segments_element_type); | ||
} | ||
|
||
const auto output_shapes = shape_infer(this, ov::util::get_node_input_partial_shapes(*this)); | ||
set_output_type(0, get_input_element_type(0), output_shapes[0]); | ||
} | ||
|
||
std::shared_ptr<Node> SegmentMax::clone_with_new_inputs(const ov::OutputVector& new_args) const { | ||
OV_OP_SCOPE(v16_SegmentMax_clone_with_new_inputs); | ||
check_new_args_count(this, new_args); | ||
if (new_args.size() == 3) { | ||
return std::make_shared<SegmentMax>(new_args.at(0), new_args.at(1), new_args.at(2), m_fill_mode); | ||
} else { | ||
return std::make_shared<SegmentMax>(new_args.at(0), new_args.at(1), m_fill_mode); | ||
} | ||
} | ||
|
||
const op::FillMode SegmentMax::get_fill_mode() const { | ||
return m_fill_mode; | ||
} | ||
|
||
} // namespace v16 | ||
} // namespace op | ||
} // namespace ov |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is specific for SegmentMax op only, then it should be placed in the op dedicated namespace and file.
Otherwise generalize the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a chance to extend it and reuse it with other ops in the future like SegmentMin
But if it's preferred to use a separate enums for such ops, it should be inside the SegmentMax class/namespace/file.