Skip to content
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

[Opset13][FP8] Introduce FakeConvert op core #20930

Merged
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/core/include/openvino/op/fake_convert.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#pragma once

#include "openvino/op/op.hpp"

namespace ov {
namespace op {
namespace v13 {
/// \ingroup ov_ops_cpp_api
class OPENVINO_API FakeConvert : public Op {
public:
OPENVINO_OP("FakeConvert", "opset13");

FakeConvert() = default;
FakeConvert(const ov::Output<ov::Node>& arg,
const ov::Output<ov::Node>& scale,
const ov::Output<ov::Node>& shift,
std::string destination_type = "f8e4m3",
bool apply_scale = false);
Copy link
Contributor

@slyalin slyalin Nov 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason to have apply_scale set to false by default, suggesting to set it automatically in case if scale input is provided (and sale and shift inputs are made optional as suggested in another comment). If one wants to provide shift but doesn't apply scale, then as we don't have "gaps" in the list of operation arguments, one should provide arbitrary scale (will be ignored), then shift and set apply_scale to false. This is only (and looks like very rare situation) when apply_scale should have manual control as a parameter of constructor.

Copy link
Contributor

@slyalin slyalin Nov 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FakeConvert(input) ==> apply_scale = false (as no scale provided)
FakeConvert(input, scale) ==> apply_scale = true
FakeConvert(input, scale, shift) ==> apply_scale = true
FakeConvert(input, scale, shift, "f8e4m", false) ==> apply_scale = false, scale input is ignored

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why I started the conversation above: #20930 (comment)
So the final suggestion is to have three constructors and the apply_scale attribute.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, in the updated version scale input is required and shift input optional.
The apply_scale attribute has been removed.


void validate_and_infer_types() override;
std::shared_ptr<ov::Node> clone_with_new_inputs(const ov::OutputVector& new_args) const override;
bool visit_attributes(ov::AttributeVisitor& visitor) override;
bool has_evaluate() const override;

bool get_apply_scale() const;
const std::string& get_destination_type() const;

private:
void validate_type() const;

std::string m_destination_type = "f8e4m3";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this string? Should be ov::element::Type. See Convert class for reference -- no reason to have an alternative way to describe the type.
Sorry, missed this when reviewing because didn't really expect a trap here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bool m_apply_scale = false;
};
} // namespace v13
} // namespace op
} // namespace ov
1 change: 1 addition & 0 deletions src/core/include/openvino/op/ops.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
#include "openvino/op/experimental_detectron_topkrois.hpp"
#include "openvino/op/extractimagepatches.hpp"
#include "openvino/op/eye.hpp"
#include "openvino/op/fake_convert.hpp"
#include "openvino/op/fake_quantize.hpp"
#include "openvino/op/floor.hpp"
#include "openvino/op/floor_mod.hpp"
Expand Down
1 change: 1 addition & 0 deletions src/core/include/openvino/opsets/opset13_tbl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,4 @@ _OPENVINO_OP_REG(BitwiseXor, ov::op::v13)
_OPENVINO_OP_REG(NMSRotated, ov::op::v13)
_OPENVINO_OP_REG(Multinomial, ov::op::v13)
_OPENVINO_OP_REG(ScaledDotProductAttention, ov::op::v13)
_OPENVINO_OP_REG(FakeConvert, ov::op::v13)
74 changes: 74 additions & 0 deletions src/core/src/op/fake_convert.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#include "openvino/op/fake_convert.hpp"

#include "itt.hpp"

namespace ov {
namespace op {
namespace v13 {
namespace fake_convert {
static const std::vector<std::string>& get_valid_types() {
static const std::vector<std::string> valid_types{"f8e4m3", "f8e5m2"};
return valid_types;
}
} // namespace fake_convert
FakeConvert::FakeConvert(const ov::Output<ov::Node>& arg,
const ov::Output<ov::Node>& scale,
const ov::Output<ov::Node>& shift,
std::string destination_type,
bool apply_scale)
: Op({arg, scale, shift}),
m_destination_type(std::move(destination_type)),
m_apply_scale(apply_scale) {
constructor_validate_and_infer_types();
}

bool FakeConvert::get_apply_scale() const {
return m_apply_scale;
}

const std::string& FakeConvert::get_destination_type() const {
return m_destination_type;
}

void FakeConvert::validate_and_infer_types() {
OV_OP_SCOPE(v13_FakeConvert_validate_and_infer_types);
validate_type();
set_output_type(0, get_input_element_type(0), get_input_partial_shape(0));
}

std::shared_ptr<ov::Node> FakeConvert::clone_with_new_inputs(const ov::OutputVector& new_args) const {
OV_OP_SCOPE(v13_FakeConvert_clone_with_new_inputs);
OPENVINO_ASSERT(new_args.size() == 3, "Incorrect number of new arguments");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As scale and shift are not mandatory, please make them optional. Otherwise I don't understand why we have apply_scale, but don't have apply_shift.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you make both scale and shift optional, you really need to have only apply_scale among attributes. Otherwise apply_shift is required.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make the shift an optional parameter. I would make scale as required.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we always need to apply scale, but shift can be optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, in the updated version scale input is required and shift input optional.
The apply_scale attribute has been removed.


return std::make_shared<FakeConvert>(new_args.at(0),
new_args.at(1),
new_args.at(2),
m_destination_type,
m_apply_scale);
}

bool FakeConvert::visit_attributes(ov::AttributeVisitor& visitor) {
OV_OP_SCOPE(v13_FakeConvert_visit_attributes);
visitor.on_attribute("destination_type", m_destination_type);
visitor.on_attribute("apply_scale", m_apply_scale);

return true;
}

void FakeConvert::validate_type() const {
const auto& valid_types = fake_convert::get_valid_types();
OPENVINO_ASSERT(std::find(valid_types.begin(), valid_types.end(), m_destination_type) != valid_types.end(),
"Bad format for f8 conversion type: " + m_destination_type);
}

bool FakeConvert::has_evaluate() const {
return false;
}

} // namespace v13
} // namespace op
} // namespace ov
2 changes: 1 addition & 1 deletion src/core/tests/opset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ INSTANTIATE_TEST_SUITE_P(opset,
OpsetTestParams{ov::get_opset10, 177},
OpsetTestParams{ov::get_opset11, 177},
OpsetTestParams{ov::get_opset12, 178},
OpsetTestParams{ov::get_opset13, 185}),
OpsetTestParams{ov::get_opset13, 186}),
OpsetTestNameGenerator{});

class MyOpOld : public ov::op::Op {
Expand Down
42 changes: 42 additions & 0 deletions src/core/tests/type_prop/fake_convert.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#include "openvino/op/fake_convert.hpp"

#include <gtest/gtest.h>

#include "common_test_utils/type_prop.hpp"

using namespace ov;
using ov::op::v0::Parameter;

TEST(type_prop, fake_convert_basic_f32) {
const auto data = std::make_shared<Parameter>(element::f32, PartialShape{2, 3, 8, 6});
const auto scale = std::make_shared<Parameter>(element::f32, PartialShape{});
const auto shift = std::make_shared<Parameter>(element::f32, PartialShape{});

const auto op = std::make_shared<op::v13::FakeConvert>(data, scale, shift);
EXPECT_EQ(op->get_output_element_type(0), element::f32);
EXPECT_EQ(op->get_output_partial_shape(0), (PartialShape{2, 3, 8, 6}));
}

TEST(type_prop, fake_convert_basic_f16) {
const auto data = std::make_shared<Parameter>(element::f16, PartialShape{2, 3, 8, 6});
const auto scale = std::make_shared<Parameter>(element::f16, PartialShape{});
const auto shift = std::make_shared<Parameter>(element::f16, PartialShape{});

const auto op = std::make_shared<op::v13::FakeConvert>(data, scale, shift);
EXPECT_EQ(op->get_output_element_type(0), element::f16);
EXPECT_EQ(op->get_output_partial_shape(0), (PartialShape{2, 3, 8, 6}));
}

TEST(type_prop, fake_convert_dynamic_shape) {
const auto data = std::make_shared<Parameter>(element::f32, PartialShape::dynamic());
const auto scale = std::make_shared<Parameter>(element::f32, PartialShape{});
const auto shift = std::make_shared<Parameter>(element::f32, PartialShape{});

const auto op = std::make_shared<op::v13::FakeConvert>(data, scale, shift);
EXPECT_EQ(op->get_output_element_type(0), element::f32);
EXPECT_EQ(op->get_output_partial_shape(0), (PartialShape::dynamic()));
}
49 changes: 49 additions & 0 deletions src/core/tests/visitors/op/fake_convert.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#include "openvino/op/fake_convert.hpp"

#include <gtest/gtest.h>

#include "visitors/visitors.hpp"

using ov::Shape;
using ov::op::v0::Parameter;
using ov::test::NodeBuilder;

TEST(attributes, fake_convert_v13_attributes_default) {
using ov::op::v13::FakeConvert;
NodeBuilder::get_ops().register_factory<FakeConvert>();
const auto data = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{2, 3, 8, 6});
const auto scale = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{});
const auto shift = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{});

const auto op = std::make_shared<FakeConvert>(data, scale, shift);

NodeBuilder builder(op, {data, scale, shift});
auto g_op = ov::as_type_ptr<FakeConvert>(builder.create());

EXPECT_EQ(g_op->get_apply_scale(), op->get_apply_scale());
EXPECT_EQ(g_op->get_destination_type(), op->get_destination_type());
EXPECT_EQ(g_op->get_output_element_type(0), op->get_output_element_type(0));
EXPECT_EQ(g_op->get_output_partial_shape(0), op->get_output_partial_shape(0));
}

TEST(attributes, fake_convert_v13_attributes_custom) {
using ov::op::v13::FakeConvert;
NodeBuilder::get_ops().register_factory<FakeConvert>();
const auto data = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{2, 3, 8, 6});
const auto scale = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{});
const auto shift = std::make_shared<Parameter>(ov::element::f32, ov::PartialShape{});

const auto op = std::make_shared<FakeConvert>(data, scale, shift, "f8e5m2", true);

NodeBuilder builder(op, {data, scale, shift});
auto g_op = ov::as_type_ptr<FakeConvert>(builder.create());

EXPECT_EQ(g_op->get_apply_scale(), op->get_apply_scale());
EXPECT_EQ(g_op->get_destination_type(), op->get_destination_type());
EXPECT_EQ(g_op->get_output_element_type(0), op->get_output_element_type(0));
EXPECT_EQ(g_op->get_output_partial_shape(0), op->get_output_partial_shape(0));
}
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,15 @@ std::shared_ptr<ov::Model> generate(const std::shared_ptr<ov::op::v13::BitwiseNo
return std::make_shared<ov::Model>(results, ov::ParameterVector{param}, "BitwiseNotGraph");
}

std::shared_ptr<ov::Model> generate(const std::shared_ptr<ov::op::v13::FakeConvert>& node) {
const auto data = std::make_shared<ov::op::v0::Parameter>(ov::element::f32, ov::PartialShape{2, 3, 8, 6});
const auto scale = std::make_shared<ov::op::v0::Parameter>(ov::element::f32, ov::PartialShape{});
const auto shift = std::make_shared<ov::op::v0::Parameter>(ov::element::f32, ov::PartialShape{});
const auto op = std::make_shared<ov::op::v13::FakeConvert>(data, scale, shift, "f8e4m3", true);
ov::ResultVector results{std::make_shared<ov::op::v0::Result>(op)};
return std::make_shared<ov::Model>(results, ov::ParameterVector{data, scale, shift}, "FakeConvert");
}

std::shared_ptr<ov::Model> generateArithmeticReductionKeepDims(const std::shared_ptr<ov::op::Op> &node) {
const auto data = std::make_shared<ov::op::v0::Parameter>(ov::element::f32, ov::PartialShape{3, 3});
const auto axes = ov::op::v0::Constant::create(ov::element::i32, {1}, {1});
Expand Down
Loading