[core]Migrate Convert operator to new API (#20961)

* Migrate Convert operator to new API

* Unify Convert reference for non-LP types

* Correct evaluate when output tensor not allocated

* Correct input type name

* Restore static_cast for bool as char
This commit is contained in:
Pawel Raasz authored and GitHub committed 2023-11-16 09:06:07 +00:00
1 parent aee4865b2f
commit 78bbe03ae8
3 files changed
+171 -223

No files matched your search

+1 -3
View File
@@ -40,9 +40,7 @@ public:
m_destination_type = destination_type;
}
OPENVINO_SUPPRESS_DEPRECATED_START
bool evaluate(const HostTensorVector& outputs, const HostTensorVector& inputs) const override;
OPENVINO_SUPPRESS_DEPRECATED_END
bool evaluate(TensorVector& outputs, const TensorVector& inputs) const override;
bool has_evaluate() const override;
bool evaluate_lower(TensorVector& outputs) const override;
bool evaluate_upper(TensorVector& outputs) const override;
@@ -4,6 +4,7 @@
#pragma once
#include <algorithm>
#include <cstddef>
#include "openvino/core/type/element_type.hpp"
@@ -94,13 +95,21 @@ void lp_convert(const TI* arg, TO* out, size_t count, element::Type_t src_type,
}
}
}
template <typename TI, typename TO>
typename std::enable_if<!std::is_same<TO, char>::value, TO>::type convert(const TI v) {
return static_cast<TO>(v);
}
template <typename TI, typename TO>
typename std::enable_if<std::is_same<TO, char>::value, TO>::type convert(const TI v) {
return static_cast<char>(static_cast<bool>(v));
}
} // namespace detail
template <typename TI, typename TO>
typename std::enable_if<!std::is_same<TO, char>::value>::type convert(const TI* arg, TO* out, size_t count) {
for (size_t i = 0; i < count; ++i) {
out[i] = static_cast<TO>(arg[i]);
}
void convert(const TI* arg, TO* out, const size_t count) {
std::transform(arg, arg + count, out, detail::convert<TI, TO>);
}
#if defined(OPENVINO_ARCH_X86) || defined(OPENVINO_ARCH_X86_64)
@@ -123,14 +132,5 @@ size_t count_out_of_f16_range(const float* arg, size_t count);
// Convert values from f32 to f16 with claming to f16 min/max when value is out of normal finite numbers range
void convert_from_f32_to_f16_with_clamp(const float* arg, float16* out, size_t count);
// overload to handle ov::boolean (it is stored as char)
template <typename TI, typename TO>
typename std::enable_if<std::is_same<TO, char>::value>::type convert(const TI* arg, TO* out, size_t count) {
for (size_t i = 0; i < count; ++i) {
out[i] = static_cast<char>(static_cast<bool>(arg[i]));
}
}
} // namespace reference
} // namespace ov
+157 -207
View File
@@ -2,248 +2,198 @@
// SPDX-License-Identifier: Apache-2.0
//
#include "ngraph/op/convert.hpp"
#include <memory>
#include <ngraph/validation_util.hpp>
#include "openvino/op/convert.hpp"
#include "element_visitor.hpp"
#include "itt.hpp"
#include "ngraph/op/equal.hpp"
#include "ngraph/op/select.hpp"
#include "openvino/op/equal.hpp"
#include "openvino/op/select.hpp"
#include "openvino/reference/convert.hpp"
#include "validation_util.hpp"
using namespace std;
using namespace ngraph;
namespace ov {
namespace op {
namespace convert {
op::Convert::Convert(const Output<Node>& arg, const element::Type& destination_type)
constexpr bool is_lp_type(const element::Type_t et) {
return (et == element::i4) || (et == element::u1) || (et == element::u4) || (et == element::nf4);
}
#define CONVERT_ET_LIST boolean, bf16, f16, f32, f64, i4, i8, i16, i32, i64, u1, u4, u8, u16, u32, u64, nf4
struct Evaluate : public element::NoAction<bool> {
using element::NoAction<bool>::visit;
template <element::Type_t ET, class TI = fundamental_type_for<ET>>
static result_type visit(const Tensor& arg, Tensor& out, const size_t count) {
using namespace ov::element;
return IfTypeOf<CONVERT_ET_LIST>::apply<EvalByOutputType<is_lp_type(ET)>>(
out.get_element_type(),
reinterpret_cast<const TI*>(arg.data()),
out,
count,
ET);
}
private:
template <bool IS_ARG_ET_LP>
struct EvalByOutputType : public element::NoAction<bool> {
using element::NoAction<bool>::visit;
template <element::Type_t ET,
class T,
class T_ET,
class U = ov::fundamental_type_for<ET>,
typename std::enable_if<is_lp_type(ET) || IS_ARG_ET_LP>::type* = nullptr>
static result_type visit(const T* arg, Tensor& out, const size_t count, T_ET&& arg_et) {
reference::detail::lp_convert(arg, reinterpret_cast<U*>(out.data()), count, arg_et, ET);
return true;
}
template <element::Type_t ET,
class T,
class T_ET,
class U = ov::fundamental_type_for<ET>,
typename std::enable_if<!is_lp_type(ET) && !IS_ARG_ET_LP>::type* = nullptr>
static result_type visit(const T* arg, Tensor& out, const size_t count, T_ET&&) {
reference::convert(arg, out.data<U>(), count);
return true;
}
};
};
namespace {
bool evaluate_bound(const Node* const node, TensorVector& output_values, const Tensor& input_bound) {
OPENVINO_ASSERT(node, output_values.size() == 1);
if (input_bound) {
const auto& in_bound_shape = input_bound.get_shape();
if (is_vector(in_bound_shape) && (in_bound_shape[0] == 0)) {
return true;
}
const auto is_integral_up_to_16_bits = [](const element::Type& et) -> bool {
return et.is_integral() && et.bitwidth() <= 16;
};
const auto& input_et = input_bound.get_element_type();
const auto& output_et = output_values[0].get_element_type();
const auto status = node->evaluate(output_values, {input_bound});
if (!status || is_integral_up_to_16_bits(input_et) || is_integral_up_to_16_bits(output_et)) {
return status;
}
// constants for dynamic values translation
const auto input_max = ov::util::make_tensor_of_max_value(input_et);
const auto output_max = ov::util::make_tensor_of_max_value(output_et);
if (!input_max || !output_max)
return false;
// dynamic values translation
auto input_dynamic_mask = Tensor(element::boolean, in_bound_shape);
auto outputs = TensorVector{input_dynamic_mask};
return v1::Equal().evaluate(outputs, {input_bound, input_max}) &&
v1::Select().evaluate(output_values, {input_dynamic_mask, output_max, output_values[0]});
} else {
return false;
}
}
} // namespace
} // namespace convert
namespace v0 {
Convert::Convert(const Output<Node>& arg, const element::Type& destination_type)
: Op({arg}),
m_destination_type(destination_type) {
constructor_validate_and_infer_types();
}
void op::Convert::validate_and_infer_types() {
void Convert::validate_and_infer_types() {
OV_OP_SCOPE(v0_Convert_validate_and_infer_types);
set_output_type(0, m_destination_type, get_input_partial_shape(0));
}
bool op::Convert::visit_attributes(AttributeVisitor& visitor) {
bool Convert::visit_attributes(AttributeVisitor& visitor) {
OV_OP_SCOPE(v0_Convert_visit_attributes);
visitor.on_attribute("destination_type", m_destination_type);
return true;
}
shared_ptr<Node> op::Convert::clone_with_new_inputs(const OutputVector& new_args) const {
std::shared_ptr<Node> Convert::clone_with_new_inputs(const OutputVector& new_args) const {
OV_OP_SCOPE(v0_Convert_clone_with_new_inputs);
check_new_args_count(this, new_args);
return make_shared<Convert>(new_args.at(0), m_destination_type);
return std::make_shared<Convert>(new_args.at(0), m_destination_type);
}
OPENVINO_SUPPRESS_DEPRECATED_START
namespace convert {
namespace {
template <element::Type_t INPUT_ET, element::Type_t OUTPUT_ET>
bool evaluate(const HostTensorPtr& arg, const HostTensorPtr& out) {
out->set_shape(arg->get_shape());
size_t element_count = shape_size(out->get_shape());
if ((INPUT_ET != arg->get_element_type()) || OUTPUT_ET != out->get_element_type()) {
return false;
}
if (((INPUT_ET == element::u1) || (OUTPUT_ET == element::u1)) ||
((INPUT_ET == element::u4) || (OUTPUT_ET == element::u4)) ||
((INPUT_ET == element::i4) || (OUTPUT_ET == element::i4)) ||
((INPUT_ET == element::nf4) || (OUTPUT_ET == element::nf4))) {
ov::reference::detail::lp_convert(arg->get_data_ptr<INPUT_ET>(),
out->get_data_ptr<OUTPUT_ET>(),
element_count,
INPUT_ET,
OUTPUT_ET);
} else {
ov::reference::convert(arg->get_data_ptr<INPUT_ET>(), out->get_data_ptr<OUTPUT_ET>(), element_count);
}
return true;
}
#define TYPE_OUT_CASE(a, ...) \
case element::Type_t::a: { \
OV_OP_SCOPE(OV_PP_CAT3(evaluate_covert_out, _, a)); \
rc = evaluate<INPUT_ET, element::Type_t::a>(__VA_ARGS__); \
} break
template <element::Type_t INPUT_ET>
bool evaluate(const HostTensorPtr& arg, const HostTensorPtr& out) {
bool rc = true;
switch (out->get_element_type()) {
TYPE_OUT_CASE(i4, arg, out);
TYPE_OUT_CASE(i8, arg, out);
TYPE_OUT_CASE(i16, arg, out);
TYPE_OUT_CASE(i32, arg, out);
TYPE_OUT_CASE(i64, arg, out);
TYPE_OUT_CASE(u1, arg, out);
TYPE_OUT_CASE(u4, arg, out);
TYPE_OUT_CASE(u8, arg, out);
TYPE_OUT_CASE(u16, arg, out);
TYPE_OUT_CASE(u32, arg, out);
TYPE_OUT_CASE(u64, arg, out);
TYPE_OUT_CASE(bf16, arg, out);
TYPE_OUT_CASE(f16, arg, out);
TYPE_OUT_CASE(f32, arg, out);
TYPE_OUT_CASE(f64, arg, out);
TYPE_OUT_CASE(boolean, arg, out);
TYPE_OUT_CASE(nf4, arg, out);
default:
rc = false;
break;
}
return rc;
}
bool evaluate_convert(const HostTensorPtr& arg, const HostTensorPtr& out) {
bool rc = true;
switch (arg->get_element_type()) {
OPENVINO_TYPE_CASE(evaluate_convert, u1, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, u4, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, u8, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, u16, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, u32, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, u64, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, i4, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, i8, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, i16, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, i32, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, i64, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, bf16, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, f16, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, f32, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, f64, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, boolean, arg, out);
OPENVINO_TYPE_CASE(evaluate_convert, nf4, arg, out);
default:
rc = false;
break;
}
return rc;
}
bool evaluate_bound(const Node* node, ov::TensorVector& output_values, bool is_upper) {
OPENVINO_ASSERT(node, output_values.size() == 1);
const auto& input = node->input_value(0);
if (const auto& value = is_upper ? input.get_tensor().get_upper_value() : input.get_tensor().get_lower_value()) {
if (is_vector(value.get_shape()) && (value.get_shape().front() == 0)) {
return true;
}
bool status = node->evaluate(output_values, {value});
if (!status)
return status;
const auto& input_element_type = input.get_element_type();
const auto& output_element_type = output_values[0].get_element_type();
if ((input_element_type.is_integral() && input_element_type.bitwidth() <= 16) ||
(output_element_type.is_integral() && output_element_type.bitwidth() <= 16)) {
return status;
}
// constants for dynamic values translation
OPENVINO_SUPPRESS_DEPRECATED_START
auto input_maximum_value = get_constant_max_of_type(input_element_type);
auto output_maximum_value = get_constant_max_of_type(output_values[0].get_element_type());
OPENVINO_SUPPRESS_DEPRECATED_END
if (input_maximum_value == nullptr || output_maximum_value == nullptr)
return false;
auto input_max = ov::Tensor(input_maximum_value->get_element_type(), input_maximum_value->get_shape());
memcpy(input_max.data(), input_maximum_value->get_data_ptr(), input_max.get_byte_size());
auto output_max = ov::Tensor(output_maximum_value->get_element_type(), output_maximum_value->get_shape());
memcpy(output_max.data(), output_maximum_value->get_data_ptr(), output_max.get_byte_size());
// dynamic values translation
auto input_dynamic_mask = ov::Tensor(element::boolean, input.get_shape());
auto outputs = ov::TensorVector{input_dynamic_mask};
status = op::v1::Equal().evaluate(outputs, {value, input_max});
if (!status)
return status;
status = op::v1::Select().evaluate(output_values, {input_dynamic_mask, output_max, output_values[0]});
return status;
} else
return false;
}
} // namespace
} // namespace convert
bool op::v0::Convert::evaluate(const HostTensorVector& output_values, const HostTensorVector& input_values) const {
bool Convert::evaluate(TensorVector& outputs, const TensorVector& inputs) const {
OV_OP_SCOPE(v0_Convert_evaluate);
OPENVINO_SUPPRESS_DEPRECATED_START
OPENVINO_ASSERT(validate_host_tensor_vector(input_values, 1));
OPENVINO_ASSERT(validate_host_tensor_vector(output_values, 1));
OPENVINO_SUPPRESS_DEPRECATED_END
return convert::evaluate_convert(input_values[0], output_values[0]);
OPENVINO_ASSERT(outputs.size() == 1);
OPENVINO_ASSERT(inputs.size() == 1);
if (auto& out = outputs[0]) {
const auto& in = inputs[0];
const auto& in_shape = in.get_shape();
out.set_shape(in_shape);
using namespace ov::element;
return IfTypeOf<CONVERT_ET_LIST>::apply<convert::Evaluate>(in.get_element_type(),
in,
out,
shape_size(in_shape));
} else {
return false;
}
}
bool op::v0::Convert::has_evaluate() const {
bool Convert::has_evaluate() const {
OV_OP_SCOPE(v0_Convert_has_evaluate);
switch (get_input_element_type(0)) {
case ngraph::element::u1:
case ngraph::element::u4:
case ngraph::element::u8:
case ngraph::element::u16:
case ngraph::element::u32:
case ngraph::element::u64:
case ngraph::element::i4:
case ngraph::element::i8:
case ngraph::element::i16:
case ngraph::element::i32:
case ngraph::element::i64:
case ngraph::element::bf16:
case ngraph::element::f16:
case ngraph::element::f32:
case ngraph::element::f64:
case ngraph::element::boolean:
break;
default:
return false;
}
switch (get_output_element_type(0)) {
case ngraph::element::i4:
case ngraph::element::i8:
case ngraph::element::i16:
case ngraph::element::i32:
case ngraph::element::i64:
case ngraph::element::u1:
case ngraph::element::u4:
case ngraph::element::u8:
case ngraph::element::u16:
case ngraph::element::u32:
case ngraph::element::u64:
case ngraph::element::bf16:
case ngraph::element::f16:
case ngraph::element::f32:
case ngraph::element::f64:
case ngraph::element::boolean:
break;
default:
return false;
}
return true;
const auto is_valid_type = [](const element::Type& et) -> bool {
switch (et) {
case element::boolean:
case element::bf16:
case element::f16:
case element::f32:
case element::f64:
case element::i4:
case element::i8:
case element::i16:
case element::i32:
case element::i64:
case element::u1:
case element::u4:
case element::u8:
case element::u16:
case element::u32:
case element::u64:
case element::nf4:
return true;
default:
return false;
};
};
return is_valid_type(get_input_element_type(0)) && is_valid_type(get_output_element_type(0));
}
bool op::v0::Convert::evaluate_lower(ov::TensorVector& output_values) const {
return convert::evaluate_bound(this, output_values, false);
bool Convert::evaluate_lower(TensorVector& output_values) const {
return convert::evaluate_bound(this, output_values, get_input_tensor(0).get_lower_value());
}
bool op::v0::Convert::evaluate_upper(ov::TensorVector& output_values) const {
return convert::evaluate_bound(this, output_values, true);
bool Convert::evaluate_upper(TensorVector& output_values) const {
return convert::evaluate_bound(this, output_values, get_input_tensor(0).get_upper_value());
}
bool op::v0::Convert::evaluate_label(TensorLabelVector& output_labels) const {
bool Convert::evaluate_label(TensorLabelVector& output_labels) const {
const auto input_labels = get_input_tensor(0).get_value_label();
if (input_labels.empty())
if (input_labels.empty()) {
return false;
output_labels[0] = input_labels;
return true;
} else {
output_labels[0] = input_labels;
return true;
}
}
} // namespace v0
} // namespace op
} // namespace ov