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

[Good First Issue][TF FE]: Support complex tensors for PadV2 operations #22955

Closed
rkazants opened this issue Feb 20, 2024 · 12 comments · Fixed by #23388
Closed

[Good First Issue][TF FE]: Support complex tensors for PadV2 operations #22955

rkazants opened this issue Feb 20, 2024 · 12 comments · Fixed by #23388
Assignees
Labels
category: TF FE OpenVINO TensorFlow FrontEnd gsoc-prerequisite-task Prerequisite task related to Google Summer of Code projects no_stale Do not mark as stale

Comments

@rkazants
Copy link
Contributor

Context

OpenVINO component responsible for support of TensorFlow models is called as TensorFlow Frontend (TF FE). TF FE converts a model represented in TensorFlow opset to a model in OpenVINO opset.
Some audio models use tensors of complex type. Complex type tensor is a tensor that has elements of complex type. For example, 1D tensor with three elements x = [1+2j, 2, -2j].

For supporting PadV2 operation on complex type tensor, you need to extend the corresponding loader for PadV2.

What needs to be done?

The existing loader for PadV2 needs to be extended by propagating ComplexTypeMark from input to output and to represent output complex type tensor as a floating-point type tensor with auxiliary dimension that concatenates real and imaginary parts of complex tensor.
To validate the extension, the corresponding layer test needs to be updated with complex tensor cases.

Here is an example of how to extend Reshape loader to support complex type tensors:

OutputVector translate_reshape_op(const NodeContext& node) {
    default_op_checks(node, 2, {"Reshape"}, true);
    auto tensor = node.get_input(0);
    auto complex_type_mark = as_type_ptr<ComplexTypeMark>(tensor.get_node_shared_ptr());
    auto shape = node.get_input(1);
    if (complex_type_mark) {
        element::Type complex_part_type = complex_type_mark->get_complex_part_type();
        tensor = complex_type_mark->input_value(0);

        OutputVector concat_inputs;
        concat_inputs.push_back(shape);
        concat_inputs.push_back(make_shared<v0::Constant>(shape.get_element_type(), Shape{1}, 2));

        auto concat = make_shared<v0::Concat>(concat_inputs, 0);
        auto reshape = make_shared<v1::Reshape>(tensor, concat, false);
        set_node_name(node.get_name(), reshape);
        auto complex_reshape = make_shared<ComplexTypeMark>(reshape, complex_part_type);
        return {complex_reshape->output(0)};
    }

    auto reshape = make_shared<v1::Reshape>(tensor, shape, false);
    set_node_name(node.get_name(), reshape);
    return {reshape};
}

Since OpenVINO does not have native support of complex tensors, we handle complex type in intermediate layers by representing them as a floating-point type with additional dimension (specially created) to store real and imaginary parts of the original complex tensor so slicing by the last dimension will give either real or imaginary parts: x[...,0] - real and x[...,1] - imaginary parts.

On the first step, we update default_op_checks with true flag to indicate that loader for Reshape operation now handles complex tensors:

default_op_checks(node, 2, {"Reshape"}, true);

Secondly, we check if complex type mark exists by anticipated inputs. This mark indicates that input tensor of complex type:

auto complex_type_mark = as_type_ptr<ComplexTypeMark>(tensor.get_node_shared_ptr());

Thirdly, we retrieve a floating-point tensor (with additional dimension to store real and imaginary parts) simulating complex tensor:

tensor = complex_type_mark->input_value(0);

After that, we implement conversion for Reshape for this particular case. Since a floating-point tensor simulating complex tensor has additional dimension equal to 2,
we update input target shape by appending 2 value and perform reshape on a floating-point tensor simulating complex tensor.

Finally, since Reshape should produce complex tensor by output we insert a new mark ComplexTypeMark into the output.

To validate support of complex tensors for Reshape, the new layer test TestComplexReshape was added.

Example how to run the layer test:

export TEST_DEVICE=CPU
cd openvino/tests/layer_tests/tensorflow_tests
pytest test_tf_Reshape.py

Example Pull Requests

Resources

Contact points

  • @openvinotoolkit/openvino-tf-frontend-maintainers
  • rkazants in Discord

Ticket

No response

@rkazants rkazants added no_stale Do not mark as stale category: TF FE OpenVINO TensorFlow FrontEnd good first issue Good for newcomers labels Feb 20, 2024
@github-project-automation github-project-automation bot moved this to Contributors Needed in Good first issues Feb 20, 2024
@rkazants rkazants added gsoc-prerequisite-task Prerequisite task related to Google Summer of Code projects and removed good first issue Good for newcomers labels Feb 22, 2024
@awayzjj
Copy link
Contributor

awayzjj commented Feb 26, 2024

.take

Copy link
Contributor

Thank you for looking into this issue! Please let us know if you have any questions or require any help.

@mlukasze mlukasze moved this from Contributors Needed to Assigned in Good first issues Feb 26, 2024
@awayzjj
Copy link
Contributor

awayzjj commented Feb 28, 2024

@rkazants Hi,I wonder why some Ops(such as translate_einsum_op) return vector of Op, like return {einsum}.

OutputVector translate_einsum_op(const NodeContext& node) {
    default_op_checks(node, 1, {"Einsum"});
   /*...*/
    OutputVector inputs;
    for (int input_ind = 0; input_ind < input_size; ++input_ind) {
        inputs.push_back(node.get_input(input_ind));
    }

    auto einsum = make_shared<v7::Einsum>(inputs, equation);
    set_node_name(node.get_name(), einsum);
    return {einsum};
}

but some Ops (such as translate_elu_op) return Op->outputs, like return res->outputs()

OutputVector translate_elu_op(const NodeContext& node) {
    default_op_checks(node, 1, {"Elu", "ELU"});
    auto input = node.get_input(0);
    auto alpha = node.get_attribute<float>("alpha", 1.0);
    auto res = make_shared<v0::Elu>(input, alpha);

    set_node_name(node.get_name(), res);
    return res->outputs();
}

@siddhant-0707
Copy link
Contributor

Hi @awayzjj

In the case of translate_einsum_op, the Einsum operation produces a single output tensor, so it returns a vector containing just that output tensor.

On the other hand, some operations can produce multiple output tensors. For example, an operation might produce a result tensor and also a tensor that indicates whether each element of the result is valid. In these cases, the translation function needs to return all of the output tensors, so it returns the outputs() of the operation, which is a vector of all output tensors.

In the case of translate_elu_op, even though the Elu operation only produces a single output tensor, it still uses res->outputs() to return a vector of output tensors for consistency with other operations that produce multiple outputs. This also allows the translation function to be used in a generic way, regardless of how many output tensors the operation produces.

@awayzjj
Copy link
Contributor

awayzjj commented Feb 28, 2024

@siddhant-0707 Thank you for your detailed reply! So operations which only produce a single output tensor like translate_einsum_op above can also use return einsum->outputs()? While those operations producing multiple output tensors can not just use return {res}?

@siddhant-0707
Copy link
Contributor

@awayzjj Yes, that's correct. For PadV2 adding complex type support should be fairly straightforward, you can take a look at Pad complex and replace the 0 with given constant_value in node.get_input(2)

@awayzjj
Copy link
Contributor

awayzjj commented Mar 6, 2024

@siddhant-0707 @rkazants I have made several attempts, yet I am still uncertain about how to convert node.get_input(2) into a parameter that is compatible with translate_pad_base_op. Its current type is ov::frontend::tensorflow::util::UnsupportedConstant Const_1[0]:undefined[...]. The logs I have printed and the outcomes of my attempts are detailed in the comments below.

Additionally, I wonder how to print the actual tensor value of the Output<Node> type while debugging.

I would greatly appreciate any guidance or suggestions you can offer. Thank you!

OutputVector translate_padv2_op(const NodeContext& node) {
    default_op_checks(node, 3, {"PadV2"}, true);
    auto input = node.get_input(0);
    auto paddings = node.get_input(1);
    auto constant_value = node.get_input(2);
    std::cout  << constant_value << " " <<  constant_value.get_element_type().to_string() << std::endl; // ov::frontend::tensorflow::util::UnsupportedConstant Const_1[0]:undefined[...] undefined
    std::cout << paddings << " " << paddings.get_element_type().to_string()  << std::endl; // opset1::Constant Const[0]:i32[2,2] i32
    std::cout  << input << " " << input.get_element_type().to_string()  << std::endl; // util::ComplexTypeMark ComplexTypeMark_23204[0]:dynamic[...] dynamic
    if (auto complex_type_mark = as_type_ptr<ComplexTypeMark>(input.get_node_shared_ptr())) {
        input = complex_type_mark->input_value(0);
        element::Type complex_part_type = complex_type_mark->get_complex_part_type();

        auto gather_index_real = make_shared<v0::Constant>(element::i32, Shape{}, 0);
        auto gather_index_imag = make_shared<v0::Constant>(element::i32, Shape{}, 1);

        auto minus_one = make_shared<v0::Constant>(element::i32, Shape{1}, -1);

        auto x_real = make_shared<v8::Gather>(input, gather_index_real, minus_one)->output(0);
        auto x_imag = make_shared<v8::Gather>(input, gather_index_imag, minus_one)->output(0);

    
        auto constant_value_real = create_same_type_const_scalar<int32_t>(input, 3); // case 0: hard code constant_value to 3, pass test cases where constant_value equals 3.

        auto constant_value_real = constant_value; //  case1: pass constant_value directoly, error:
        //E       openvino._pyopenvino.OpConversionFailure: Check 'is_conversion_successful' failed at src/frontends/tensorflow/src/frontend.cpp:434:
        //E       FrontEnd API failed with OpConversionFailure:
        //E       [TensorFlow Frontend] Internal error, conversion is failed for Imag operation with a message:
        //E       Check 'complex_type_mark' failed at src/frontends/tensorflow_common/src/op/real_imag.cpp:33:
        //E       FrontEnd API failed with OpValidationFailureWhile validating node 'Imag':
        //E       [TensorFlow Frontend] internal error: ComplexTypeMark is not set at the input of Imag
        //E
        //E       [TensorFlow Frontend] Internal error, conversion is failed for Real operation with a message:
        //E       Check 'complex_type_mark' failed at src/frontends/tensorflow_common/src/op/real_imag.cpp:33:
        //E       FrontEnd API failed with OpValidationFailureWhile validating node 'Real':
        //E       [TensorFlow Frontend] internal error: ComplexTypeMark is not set at the input of Real
        //E
        //E       [TensorFlow Frontend] Internal error, conversion is failed for PadV2 operation with a message:
        //E       Check 'element::Type::merge(result_et, result_et, arg_pad_element_type)' failed at src/core/src/op/util/pad_base.cpp:77:
        //E       While validating node 'opset1::Pad Pad_23225 (opset8::Gather Gather_23216[0]:f32[1,2], opset8::Gather Gather_23223[0]:i64[2], opset8::Gather Gather_23224[0]:i64[2], ov::frontend::tensorflow::util::UnsupportedConstant Const_1[0]:undefined[...]) -> (dynamic[...])' with friendly_name 'Pad_23225':
        //E       Argument element types do not match (input arg element type: f32, arg_pad element type: undefined).
        //E
        //E       [TensorFlow Frontend] Internal error, no translator found for operation(s): ComplexTypeMark, Const of unknown type
        //E       To facilitate the conversion of unsupported operations, refer to Frontend Extension documentation: https://docs.openvino.ai/latest/openvino_docs_Extensibility_UG_Frontend_Extensions.html
        //
        //bin/intel64/Release/python/openvino/frontend/frontend.py:18: OpConversionFailure     

        auto constant_value_real = as_type_ptr<UnsupportedConstant>(constant_value.get_node_shared_ptr())->input_value(0); // case 2
        //E       openvino._pyopenvino.OpConversionFailure: Check 'is_conversion_successful' failed at src/frontends/tensorflow/src/frontend.cpp:434:
        //E       FrontEnd API failed with OpConversionFailure:
        //E       [TensorFlow Frontend] Internal error, conversion is failed for Imag operation with a message:
        //E       Check 'complex_type_mark' failed at src/frontends/tensorflow_common/src/op/real_imag.cpp:33:
        //E       FrontEnd API failed with OpValidationFailureWhile validating node 'Imag':
        //E       [TensorFlow Frontend] internal error: ComplexTypeMark is not set at the input of Imag
        //E
        //E       [TensorFlow Frontend] Internal error, conversion is failed for Real operation with a message:
        //E       Check 'complex_type_mark' failed at src/frontends/tensorflow_common/src/op/real_imag.cpp:33:
        //E       FrontEnd API failed with OpValidationFailureWhile validating node 'Real':
        //E       [TensorFlow Frontend] internal error: ComplexTypeMark is not set at the input of Real
        //E
        //E       [TensorFlow Frontend] Internal error, conversion is failed for PadV2 operation with a message:
        //E       Exception from src/core/src/node.cpp:590:
        //E       node index is out of range
        //E
        //E       [TensorFlow Frontend] Internal error, no translator found for operation(s): ComplexTypeMark, Const of unknown type
        //E       To facilitate the conversion of unsupported operations, refer to Frontend Extension documentation: https://docs.openvino.ai/latest/openvino_docs_Extensibility_UG_Frontend_Extensions.html
        //bin/intel64/Release/python/openvino/frontend/frontend.py:18: OpConversionFailure

        auto y_real = translate_pad_base_op(node, x_real, paddings, constant_value_real)[0];
        auto constant_zero = create_same_type_const_scalar<int32_t>(input, 0);
        auto y_imag = translate_pad_base_op(node, x_imag, paddings, constant_zero)[0];

        auto real_unsqueeze = make_shared<v0::Unsqueeze>(y_real, minus_one);
        auto imag_unsqueeze = make_shared<v0::Unsqueeze>(y_imag, minus_one);

        auto concat_result = make_shared<v0::Concat>(OutputVector{real_unsqueeze, imag_unsqueeze}, -1);

        set_node_name(node.get_name(), concat_result);
        auto complex_result = make_shared<ComplexTypeMark>(concat_result->output(0), complex_part_type);
        return {complex_result};
    }

    return translate_pad_base_op(node, input, paddings, constant_value);
}

@awayzjj
Copy link
Contributor

awayzjj commented Mar 7, 2024

@rkazants Hi, would you please provide some advice on the questions I raised above? Any suggestions! Thank you.

@rkazants
Copy link
Contributor Author

rkazants commented Mar 8, 2024

Hi @awayzjj, really sorry for the delay. Good attempts, PadV2 for complex type is a bit tricky case. It is because we should not forget that constant_values will be also of complex type and we should also anticipate ComplexTypeMark that means it is represented as floating-point type with auxiliary dimension.

So I propose complex type is to handle separately without calling translate_pad_base_op. Just separate real and imaginary parts and handle them separately by padding real or imag parts of padding value to them and concatenate them finally.

Best regards,
Roman

@awayzjj
Copy link
Contributor

awayzjj commented Mar 9, 2024

@rkazants Hi, thank you for your response! Could you please provide me with some suggestions on how to retrieve the real or imaginary parts of the constant_value?

As its element type is ov::frontend::tensorflow::util::UnsupportedConstant Const_1[0]:undefined[...], it differs from the input type, which is util::ComplexTypeMark ComplexTypeMark_23204[0]:dynamic[...] dynamic as per the cout log in my code above.

Therefore, I cannot follow the handling method used for input in your example, and get stuck here for some days.

@rkazants
Copy link
Contributor Author

rkazants commented Mar 9, 2024

@rkazants Hi, thank you for your response! Could you please provide me with some suggestions on how to retrieve the real or imaginary parts of the constant_value?

As its element type is ov::frontend::tensorflow::util::UnsupportedConstant Const_1[0]:undefined[...], it differs from the input type, which is util::ComplexTypeMark ComplexTypeMark_23204[0]:dynamic[...] dynamic as per the cout log in my code above.

Therefore, I cannot follow the handling method used for input in your example, and get stuck here for some days.

Hi @awayzjj,

constant_value should have ComplexTypeMark since it is of complex type, right? So, to retrieve real and imaginary parts, you need to perform Gather operation similar to data:

        auto constant_value_real = make_shared<v8::Gather>(constant_value, gather_index_real, minus_one)->output(0);
        auto constant_value_imag = make_shared<v8::Gather>(constant_value, gather_index_imag, minus_one)->output(0);

So perform padding for real and imaginary parts separately.

Best regards,
Roman

@awayzjj
Copy link
Contributor

awayzjj commented Mar 12, 2024

@rkazants Thank you a lot for your comments!. I've submit a PR and I'll appreciate it if you'd like to review it. :)

@p-wysocki p-wysocki moved this from Assigned to In Review in Good first issues Mar 12, 2024
github-merge-queue bot pushed a commit that referenced this issue Mar 15, 2024
Closes #22955
### Details:
- separate input into real and imaginary parts and handle them
separately by padding real or imag parts of padding value to them and
concatenate them finally.

---------

Co-authored-by: Roman Kazantsev <roman.kazantsev@intel.com>
@github-project-automation github-project-automation bot moved this from In Review to Closed in Good first issues Mar 15, 2024
bbielawx pushed a commit to bbielawx/openvino that referenced this issue Apr 12, 2024
…t#22955) (openvinotoolkit#23388)

Closes openvinotoolkit#22955
### Details:
- separate input into real and imaginary parts and handle them
separately by padding real or imag parts of padding value to them and
concatenate them finally.

---------

Co-authored-by: Roman Kazantsev <roman.kazantsev@intel.com>
alvoron pushed a commit to alvoron/openvino that referenced this issue Apr 29, 2024
…t#22955) (openvinotoolkit#23388)

Closes openvinotoolkit#22955
### Details:
- separate input into real and imaginary parts and handle them
separately by padding real or imag parts of padding value to them and
concatenate them finally.

---------

Co-authored-by: Roman Kazantsev <roman.kazantsev@intel.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
category: TF FE OpenVINO TensorFlow FrontEnd gsoc-prerequisite-task Prerequisite task related to Google Summer of Code projects no_stale Do not mark as stale
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

3 participants