From 72673c15b0d4c74cd4c4eea357b1d24390dc9bd9 Mon Sep 17 00:00:00 2001 From: Srinjoy Date: Tue, 15 Apr 2025 00:10:02 +0530 Subject: [PATCH 1/4] Add numpy min for OV Backend --- .../openvino/excluded_concrete_tests.txt | 2 - keras/src/backend/openvino/numpy.py | 78 ++++++++++++++++++- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/keras/src/backend/openvino/excluded_concrete_tests.txt b/keras/src/backend/openvino/excluded_concrete_tests.txt index c2ecf0371599..4355a4bbd175 100644 --- a/keras/src/backend/openvino/excluded_concrete_tests.txt +++ b/keras/src/backend/openvino/excluded_concrete_tests.txt @@ -35,7 +35,6 @@ NumpyDtypeTest::test_max NumpyDtypeTest::test_mean NumpyDtypeTest::test_median NumpyDtypeTest::test_meshgrid -NumpyDtypeTest::test_min NumpyDtypeTest::test_moveaxis NumpyDtypeTest::test_multiply NumpyDtypeTest::test_nan @@ -95,7 +94,6 @@ NumpyOneInputOpsCorrectnessTest::test_max NumpyOneInputOpsCorrectnessTest::test_mean NumpyOneInputOpsCorrectnessTest::test_median NumpyOneInputOpsCorrectnessTest::test_meshgrid -NumpyOneInputOpsCorrectnessTest::test_min NumpyOneInputOpsCorrectnessTest::test_moveaxis NumpyOneInputOpsCorrectnessTest::test_nan_to_num NumpyOneInputOpsCorrectnessTest::test_ndim diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index f1a998b8e939..2919efffe172 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -1006,7 +1006,83 @@ def meshgrid(*x, indexing="xy"): def min(x, axis=None, keepdims=False, initial=None): - raise NotImplementedError("`min` is not supported with openvino backend") + x = get_ov_output(x) + x_type = x.get_element_type() + x_shape = x.get_partial_shape().to_shape() + is_empty = False + for dim in x_shape: + if dim == 0: + is_empty = True + break + + if is_empty and initial is not None: + if axis is None: + result = ov_opset.constant(initial, x_type).output(0) + if keepdims: + result_shape = [1] * len(x_shape) + result = ov_opset.reshape( + result, + ov_opset.constant(result_shape, Type.i32).output(0), + False, + ).output(0) + else: + if not isinstance(axis, (list, tuple)): + axis = [axis] + result_shape = list(x_shape) + for ax in axis: + if ax < 0: + ax = len(x_shape) + ax + result_shape[ax] = 1 if keepdims else 0 + if not keepdims: + result_shape = [dim for dim in result_shape if dim != 0] + init_tensor = ov_opset.constant(initial, x_type).output(0) + if result_shape: + result = ov_opset.reshape( + init_tensor, + ov_opset.constant(result_shape, Type.i32).output(0), + False, + ).output(0) + else: + result = init_tensor + return OpenVINOKerasTensor(result) + + if axis is None and initial is not None: + flatten_shape = ov_opset.constant([-1], Type.i32).output(0) + x = ov_opset.reshape(x, flatten_shape, False).output(0) + min_axis = ov_opset.constant(0, Type.i32).output(0) + min_result = ov_opset.reduce_min(x, min_axis).output(0) + initial_tensor = ov_opset.constant(initial, x_type).output(0) + result = ov_opset.minimum(min_result, initial_tensor).output(0) + + if keepdims: + result_shape = [1] * len(x_shape) + result = ov_opset.reshape( + result, + ov_opset.constant(result_shape, Type.i32).output(0), + False, + ).output(0) + + return OpenVINOKerasTensor(result) + + if isinstance(axis, tuple) and len(axis) == 0: + return OpenVINOKerasTensor(x) + + if axis is None: + flatten_shape = ov_opset.constant([-1], Type.i32).output(0) + x = ov_opset.reshape(x, flatten_shape, False).output(0) + axis = 0 + + if isinstance(axis, tuple): + axis = list(axis) + + axis_const = ov_opset.constant(axis, Type.i32).output(0) + min_result = ov_opset.reduce_min(x, axis_const, keepdims).output(0) + + if initial is not None: + initial_tensor = ov_opset.constant(initial, x_type).output(0) + min_result = ov_opset.minimum(min_result, initial_tensor).output(0) + + return OpenVINOKerasTensor(min_result) def minimum(x1, x2): From a21dc25778649932ad7c78cd507a1984e6f3f954 Mon Sep 17 00:00:00 2001 From: Srinjoy Date: Tue, 15 Apr 2025 00:30:54 +0530 Subject: [PATCH 2/4] Add boolean case --- keras/src/backend/openvino/numpy.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 2919efffe172..14eb8821508f 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -1007,8 +1007,15 @@ def meshgrid(*x, indexing="xy"): def min(x, axis=None, keepdims=False, initial=None): x = get_ov_output(x) - x_type = x.get_element_type() + original_type = x.get_element_type() + x_type = original_type x_shape = x.get_partial_shape().to_shape() + + is_bool = x_type == Type.boolean + if is_bool: + x = ov_opset.convert(x, Type.i32).output(0) + x_type = Type.i32 + is_empty = False for dim in x_shape: if dim == 0: @@ -1044,6 +1051,10 @@ def min(x, axis=None, keepdims=False, initial=None): ).output(0) else: result = init_tensor + + if is_bool: + result = ov_opset.convert(result, Type.boolean).output(0) + return OpenVINOKerasTensor(result) if axis is None and initial is not None: @@ -1062,6 +1073,9 @@ def min(x, axis=None, keepdims=False, initial=None): False, ).output(0) + if is_bool: + result = ov_opset.convert(result, Type.boolean).output(0) + return OpenVINOKerasTensor(result) if isinstance(axis, tuple) and len(axis) == 0: @@ -1082,6 +1096,9 @@ def min(x, axis=None, keepdims=False, initial=None): initial_tensor = ov_opset.constant(initial, x_type).output(0) min_result = ov_opset.minimum(min_result, initial_tensor).output(0) + if is_bool: + min_result = ov_opset.convert(min_result, Type.boolean).output(0) + return OpenVINOKerasTensor(min_result) From e5839b0f8bea0ade4057fdea87668270929f340a Mon Sep 17 00:00:00 2001 From: Srinjoy Date: Tue, 15 Apr 2025 11:16:34 +0530 Subject: [PATCH 3/4] Fix failing tests issue --- keras/src/backend/openvino/excluded_concrete_tests.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/keras/src/backend/openvino/excluded_concrete_tests.txt b/keras/src/backend/openvino/excluded_concrete_tests.txt index 4355a4bbd175..39b609affc6b 100644 --- a/keras/src/backend/openvino/excluded_concrete_tests.txt +++ b/keras/src/backend/openvino/excluded_concrete_tests.txt @@ -35,6 +35,7 @@ NumpyDtypeTest::test_max NumpyDtypeTest::test_mean NumpyDtypeTest::test_median NumpyDtypeTest::test_meshgrid +NumpyDtypeTest::test_minimum_python_types NumpyDtypeTest::test_moveaxis NumpyDtypeTest::test_multiply NumpyDtypeTest::test_nan From 218158f609ae42611a8282af07ec28e83edda97b Mon Sep 17 00:00:00 2001 From: Srinjoy Date: Sat, 19 Apr 2025 23:50:36 +0530 Subject: [PATCH 4/4] Update implementation --- keras/src/backend/openvino/numpy.py | 70 ++++------------------------- 1 file changed, 8 insertions(+), 62 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 14eb8821508f..9ed5ecd02a87 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -1016,68 +1016,6 @@ def min(x, axis=None, keepdims=False, initial=None): x = ov_opset.convert(x, Type.i32).output(0) x_type = Type.i32 - is_empty = False - for dim in x_shape: - if dim == 0: - is_empty = True - break - - if is_empty and initial is not None: - if axis is None: - result = ov_opset.constant(initial, x_type).output(0) - if keepdims: - result_shape = [1] * len(x_shape) - result = ov_opset.reshape( - result, - ov_opset.constant(result_shape, Type.i32).output(0), - False, - ).output(0) - else: - if not isinstance(axis, (list, tuple)): - axis = [axis] - result_shape = list(x_shape) - for ax in axis: - if ax < 0: - ax = len(x_shape) + ax - result_shape[ax] = 1 if keepdims else 0 - if not keepdims: - result_shape = [dim for dim in result_shape if dim != 0] - init_tensor = ov_opset.constant(initial, x_type).output(0) - if result_shape: - result = ov_opset.reshape( - init_tensor, - ov_opset.constant(result_shape, Type.i32).output(0), - False, - ).output(0) - else: - result = init_tensor - - if is_bool: - result = ov_opset.convert(result, Type.boolean).output(0) - - return OpenVINOKerasTensor(result) - - if axis is None and initial is not None: - flatten_shape = ov_opset.constant([-1], Type.i32).output(0) - x = ov_opset.reshape(x, flatten_shape, False).output(0) - min_axis = ov_opset.constant(0, Type.i32).output(0) - min_result = ov_opset.reduce_min(x, min_axis).output(0) - initial_tensor = ov_opset.constant(initial, x_type).output(0) - result = ov_opset.minimum(min_result, initial_tensor).output(0) - - if keepdims: - result_shape = [1] * len(x_shape) - result = ov_opset.reshape( - result, - ov_opset.constant(result_shape, Type.i32).output(0), - False, - ).output(0) - - if is_bool: - result = ov_opset.convert(result, Type.boolean).output(0) - - return OpenVINOKerasTensor(result) - if isinstance(axis, tuple) and len(axis) == 0: return OpenVINOKerasTensor(x) @@ -1096,6 +1034,14 @@ def min(x, axis=None, keepdims=False, initial=None): initial_tensor = ov_opset.constant(initial, x_type).output(0) min_result = ov_opset.minimum(min_result, initial_tensor).output(0) + if keepdims: + result_shape = [1] * len(x_shape) + min_result = ov_opset.reshape( + min_result, + ov_opset.constant(result_shape, Type.i32).output(0), + False, + ).output(0) + if is_bool: min_result = ov_opset.convert(min_result, Type.boolean).output(0)