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

[CPU] Add Clamp for FakeConvertDecomposition #28651

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "itt.hpp"
#include "openvino/core/rt_info.hpp"
#include "openvino/op/add.hpp"
#include "openvino/op/clamp.hpp"
#include "openvino/op/constant.hpp"
#include "openvino/op/convert.hpp"
#include "openvino/op/divide.hpp"
Expand Down Expand Up @@ -41,20 +42,30 @@ ov::pass::FakeConvertDecomposition::FakeConvertDecomposition() {
data = decomp_ops.add(data.get_node_shared_ptr());
}

// Align with clamp behavior of FakeConvert in ngraph reference
const auto lower_bound = fake_convert_node->get_destination_element_type() == ov::element::f8e4m3
? std::numeric_limits<ov::float8_e4m3>::lowest()
: std::numeric_limits<ov::float8_e5m2>::lowest();
const auto upper_bound = fake_convert_node->get_destination_element_type() == ov::element::f8e4m3
? std::numeric_limits<ov::float8_e4m3>::max()
: std::numeric_limits<ov::float8_e5m2>::max();

std::shared_ptr<Node> result;
const auto scale = decomp_ops.make<ov::op::v1::Multiply>(data, input_scale);
if (fake_convert_node->get_input_size() == 2) {
const auto clamp = std::make_shared<ov::op::v0::Clamp>(scale, lower_bound, upper_bound);
const auto downconvert =
decomp_ops.make<ov::op::v0::Convert>(scale, fake_convert_node->get_destination_element_type());
decomp_ops.make<ov::op::v0::Convert>(clamp, fake_convert_node->get_destination_element_type());
const auto upconvert = decomp_ops.make<ov::op::v0::Convert>(downconvert, input_type);

result = decomp_ops.make<ov::op::v1::Divide>(upconvert, input_scale);
} else {
const Output<Node> input_shift{fake_convert_node->input_value(2)};
const auto shift = decomp_ops.make<ov::op::v1::Subtract>(scale, input_shift);

const auto clamp = std::make_shared<ov::op::v0::Clamp>(shift, lower_bound, upper_bound);
const auto downconvert =
decomp_ops.make<ov::op::v0::Convert>(shift, fake_convert_node->get_destination_element_type());
decomp_ops.make<ov::op::v0::Convert>(clamp, fake_convert_node->get_destination_element_type());
const auto upconvert = decomp_ops.make<ov::op::v0::Convert>(downconvert, input_type);

const auto deshift = decomp_ops.make<ov::op::v1::Add>(upconvert, input_shift);
Expand Down
3 changes: 2 additions & 1 deletion src/core/reference/src/op/fake_convert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ void emulate_f8e5m2_on_fp16(const float16* const arg_f, float16* out_f, size_t c
// 101, 110, 111 - round up > 0x0080
val_bit_repr += (((rnmask > 0x0080) || (rnmask_tie == rne_tie)) << lshift);
}
val_bit_repr &= mask_mant; /* truncation */
val_bit_repr &= mask_mant; /* truncation */
val_bit_repr -= (((val_bit_repr & 0x7F00) == fp16_inf) << lshift); /* clamp */
Copy link
Contributor Author

Choose a reason for hiding this comment

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

AFAIK, the behavior of master branch for converting different f32 values to f8e4m3 is as follows:

Value type Convert f32 to f16 Convert f16 to f8e5m2
f16 overflowed value INF INF (this PR change it to not-INF)
f8e5m2 overflowed value not-INF not-INF
f8e5m2 other value not-INF not-INF

For overflowed values, if we apply clamp in FakeConvertDecomposition we gets not-INF, otherwise we gets INF. We can not get different behavior for "f16 overflowed value" and "f8e5m2 overflowed value".

So here, I applies this revision to make "f16 overflowed value" also get not-INF result. The other reason is to align with behavior of f8e4m3.

@dmitry-gorokhov @mitruska Please feel free to comment. Thanks a lot!

out_u[i] = val_bit_repr;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ std::vector<std::string> disabledTestPatterns() {
R"(.*ConvertCPULayerTest.*outFmts=(nhwc|nChw8c|nChw16c).*)",
// Issue: MFDNN-12917. The oneDNN emitter of conversion from fp32 to fp8 has rounding issue.
R"(.*ConvertCPULayerTest.*(\[1.1.1080.1920\]|\(2.17.5.4\))_.*_inputPRC=f32_targetPRC=f8e4m3_.*)",
// Issue: 123320
Copy link
Contributor Author

@xuchen-intel xuchen-intel Jan 24, 2025

Choose a reason for hiding this comment

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

These bf16 related test cases for FakeConvert are skipped because of the following code block, which has already been tracked by ticket 123320.

The code block here

// todo: issue: 123320
if (!((inf_prc != config.end() && inf_prc->second == element::undefined)
|| (inf_prc == config.end() && exec_mode != config.end() && exec_mode->second == hint::ExecutionMode::ACCURACY))) {
test->convert_precisions.insert({ov::element::bf16, ov::element::f32});
test->convert_precisions.insert({ov::element::f16, ov::element::f32});
}
avoids input data precision of FakeConvert to be bf16 in expected ngraph reference test. So for expected FakeConvert reference input precision is fp32, while for actual FakeConvert (before decomposition) of plugins input precision is bf16 as we set. For values at the edge of rounding, the gap between expected and actual result is significant while they are both correct.

Here is a case for FakeConvert layer. data=3504, scale=0.546875, shift=0, destination_type=f8e5m2. Expected result is 3264, actual result is 3744, but both correct.

Expected: bf16(3504) -> fp32(3504) -> *scale(0.546875) ->fp32(1916.25) -> fp16(1916) -> fp16(constrained_in_f8_range)(1792) -> fp32(1792) -> /scale(0.546875) -> fp32(3276.8) -> bf16(3264)

Actual: bf32(3504) -> *scale(0.546875) -> bf16(1920) -> fp16(1920) -> fp16(constrained_in_f8_range)(2048) -> bf16(2048) -> /scale(0.546875) ->bf16(3744)

// Input precision bf16 is converted to fp32 by logic in core_config.cpp during ngraph reference test.
R"(.*FakeConvertLayerTest.*dataPrecision=bf16.*)",
// Need to generate sequence exactly in the i64 data type. Enable in scope of i64 enabling.
R"(.*RandomUniformLayerTestCPU.*OutPrc=i64.*)",
// Issue: 123815 (Tests are sensintive to available thread count on testing machines)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include "shared_test_classes/single_op/fake_convert.hpp"

#include <random>

#include "openvino/opsets/opset1.hpp"
#include "openvino/opsets/opset13.hpp"

Expand Down Expand Up @@ -52,9 +54,24 @@ void FakeConvertLayerTest::SetUp() {

init_input_shapes(data_shapes);

std::vector<float> scale_values(ov::shape_size(scale_shape));
std::vector<float> shift_values(ov::shape_size(shift_shape));
std::mt19937 gen(0);
std::uniform_real_distribution<float> dis(0, static_cast<float>(ov::shape_size(scale_shape)));
for (auto& scale_value : scale_values)
scale_value = dis(gen);
for (auto& shift_value : shift_values)
shift_value = dis(gen);

if (data_prec == ov::element::f16) {
configuration.insert(ov::hint::inference_precision(ov::element::f16));
} else if (data_prec == ov::element::bf16) {
configuration.insert(ov::hint::inference_precision(ov::element::bf16));
}

const auto data = std::make_shared<opset1::Parameter>(data_prec, inputDynamicShapes.front());
const auto scale = std::make_shared<opset1::Constant>(data_prec, scale_shape);
const auto shift = std::make_shared<opset1::Constant>(data_prec, shift_shape);
const auto scale = std::make_shared<opset1::Constant>(data_prec, scale_shape, scale_values);
const auto shift = std::make_shared<opset1::Constant>(data_prec, shift_shape, shift_values);

const auto fake_convert = default_shift ? std::make_shared<opset13::FakeConvert>(data, scale, dst_prec)
: std::make_shared<opset13::FakeConvert>(data, scale, shift, dst_prec);
Expand Down
Loading