From f76a67e19cef400645d53f2624b29c244943f6b0 Mon Sep 17 00:00:00 2001 From: "daniel.cahall" Date: Thu, 23 Oct 2025 22:45:30 -0400 Subject: [PATCH 1/8] additional ov ops --- .../openvino/excluded_concrete_tests.txt | 11 ----- keras/src/backend/openvino/numpy.py | 43 +++++++++++-------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/keras/src/backend/openvino/excluded_concrete_tests.txt b/keras/src/backend/openvino/excluded_concrete_tests.txt index 1f2cf067ab09..68ccbc4b79b4 100644 --- a/keras/src/backend/openvino/excluded_concrete_tests.txt +++ b/keras/src/backend/openvino/excluded_concrete_tests.txt @@ -75,13 +75,6 @@ NumpyOneInputOpsCorrectnessTest::test_floor_divide NumpyOneInputOpsCorrectnessTest::test_imag NumpyOneInputOpsCorrectnessTest::test_isreal NumpyOneInputOpsCorrectnessTest::test_logaddexp2 -NumpyOneInputOpsCorrectnessTest::test_pad_float16_constant_2 -NumpyOneInputOpsCorrectnessTest::test_pad_float32_constant_2 -NumpyOneInputOpsCorrectnessTest::test_pad_float64_constant_2 -NumpyOneInputOpsCorrectnessTest::test_pad_int16_constant_2 -NumpyOneInputOpsCorrectnessTest::test_pad_int8_constant_2 -NumpyOneInputOpsCorrectnessTest::test_pad_uint8_constant_2 -NumpyOneInputOpsCorrectnessTest::test_pad_int32_constant_2 NumpyOneInputOpsCorrectnessTest::test_real NumpyOneInputOpsCorrectnessTest::test_reshape NumpyOneInputOpsCorrectnessTest::test_roll @@ -95,7 +88,6 @@ NumpyOneInputOpsCorrectnessTest::test_std NumpyOneInputOpsCorrectnessTest::test_swapaxes NumpyOneInputOpsCorrectnessTest::test_tile NumpyOneInputOpsCorrectnessTest::test_trace -NumpyOneInputOpsCorrectnessTest::test_transpose NumpyOneInputOpsCorrectnessTest::test_trapezoid NumpyOneInputOpsCorrectnessTest::test_trunc NumpyOneInputOpsCorrectnessTest::test_unravel_index @@ -112,7 +104,6 @@ NumpyTwoInputOpsCorrectnessTest::test_digitize NumpyTwoInputOpsCorrectnessTest::test_divide_no_nan NumpyTwoInputOpsCorrectnessTest::test_einsum NumpyTwoInputOpsCorrectnessTest::test_gcd -NumpyTwoInputOpsCorrectnessTest::test_heaviside NumpyTwoInputOpsCorrectnessTest::test_hypot NumpyTwoInputOpsCorrectnessTest::test_inner NumpyTwoInputOpsCorrectnessTest::test_isin @@ -136,13 +127,11 @@ NumpyOneInputOpsStaticShapeTest::test_cbrt NumpyOneInputOpsStaticShapeTest::test_isposinf NumpyOneInputOpsStaticShapeTest::test_isreal NumpyTwoInputOpsDynamicShapeTest::test_gcd -NumpyTwoInputOpsDynamicShapeTest::test_heaviside NumpyTwoInputOpsDynamicShapeTest::test_hypot NumpyTwoInputOpsDynamicShapeTest::test_isin NumpyTwoInputOpsDynamicShapeTest::test_kron NumpyTwoInputOpsDynamicShapeTest::test_lcm NumpyTwoInputOpsStaticShapeTest::test_gcd -NumpyTwoInputOpsStaticShapeTest::test_heaviside NumpyTwoInputOpsStaticShapeTest::test_hypot NumpyTwoInputOpsStaticShapeTest::test_isin NumpyTwoInputOpsStaticShapeTest::test_kron diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 88cdbf939847..100cac2d21e7 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -553,9 +553,26 @@ def hamming(x): def heaviside(x1, x2): - raise NotImplementedError( - "`heaviside` is not supported with openvino backend" - ) + x1 = get_ov_output(x1) + T = x1.get_element_type() + + x2 = get_ov_output(x2, x1.get_element_type()) + zero_scalar = ov_opset.constant(0, x1.get_element_type()).output(0) + one_scalar = ov_opset.constant(1, x1.get_element_type()).output(0) + + neg = ov_opset.less(x1, zero_scalar).output(0) + pos = ov_opset.greater(x1, zero_scalar).output(0) + eq = ov_opset.equal(x1, zero_scalar).output(0) + + x = ov_opset.select(neg, zero_scalar, x1).output(0) # dtype: x1 dtype + x = ov_opset.select(pos, one_scalar, x).output(0) # dtype: x1 dtype + + # 🔧 Make sure x2 matches x1’s dtype before the final select + x2_cast = ov_opset.convert(x2, x1.get_element_type()).output(0) + x = ov_opset.select(eq, x2_cast, x).output(0) + + x = ov_opset.convert(x, T).output(0) + return OpenVINOKerasTensor(x) def kaiser(x, beta): @@ -695,15 +712,9 @@ def count_nonzero(x, axis=None): zero_constant = ov_opset.convert_like(zero_constant, x) x = ov_opset.not_equal(x, zero_constant).output(0) x = ov_opset.convert(x, Type.i32).output(0) - 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) - if axis == []: + x, axis = _resolve_axis(x, axis) + if not axis: return OpenVINOKerasTensor(x) - axis = ov_opset.constant(axis, Type.i32).output(0) return OpenVINOKerasTensor(ov_opset.reduce_sum(x, axis, False).output(0)) @@ -722,11 +733,7 @@ def cumsum(x, axis=None, dtype=None): if dtype is not None: ov_type = OPENVINO_DTYPES[standardize_dtype(dtype)] x = ov_opset.convert(x, ov_type).output(0) - 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 - axis = ov_opset.constant(axis, Type.i32).output(0) + x, axis = _resolve_axis(x, axis) if x.get_element_type() == Type.boolean: x = ov_opset.convert(x, Type.i32).output(0) return OpenVINOKerasTensor(ov_opset.cumsum(x, axis).output(0)) @@ -1761,7 +1768,9 @@ def pad(x, pad_width, mode="constant", constant_values=None): "`pad` operation supports only scalar pad value " "in constant mode by openvino backend" ) - pad_value = constant_values + pad_value = ov_opset.constant( + constant_values, x.get_element_type() + ).output(0) # split pad_width into two tensors pads_begin and pads_end pads_begin = [] From 8c132c965834990157ebc25b41be2d7b4165cf52 Mon Sep 17 00:00:00 2001 From: "daniel.cahall" Date: Fri, 24 Oct 2025 13:14:55 -0400 Subject: [PATCH 2/8] std/var returning correct results, albeit as f64 --- .../openvino/excluded_concrete_tests.txt | 4 -- keras/src/backend/openvino/numpy.py | 37 ++++++++----------- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/keras/src/backend/openvino/excluded_concrete_tests.txt b/keras/src/backend/openvino/excluded_concrete_tests.txt index 68ccbc4b79b4..e30819423eb1 100644 --- a/keras/src/backend/openvino/excluded_concrete_tests.txt +++ b/keras/src/backend/openvino/excluded_concrete_tests.txt @@ -84,14 +84,12 @@ NumpyOneInputOpsCorrectnessTest::test_select NumpyOneInputOpsCorrectnessTest::test_signbit NumpyOneInputOpsCorrectnessTest::test_size NumpyOneInputOpsCorrectnessTest::test_slogdet -NumpyOneInputOpsCorrectnessTest::test_std NumpyOneInputOpsCorrectnessTest::test_swapaxes NumpyOneInputOpsCorrectnessTest::test_tile NumpyOneInputOpsCorrectnessTest::test_trace NumpyOneInputOpsCorrectnessTest::test_trapezoid NumpyOneInputOpsCorrectnessTest::test_trunc NumpyOneInputOpsCorrectnessTest::test_unravel_index -NumpyOneInputOpsCorrectnessTest::test_var NumpyOneInputOpsCorrectnessTest::test_vectorize NumpyOneInputOpsCorrectnessTest::test_vstack NumpyTwoInputOpsCorrectnessTest::test_bitwise_and @@ -119,12 +117,10 @@ NumpyOneInputOpsDynamicShapeTest::test_cbrt NumpyOneInputOpsDynamicShapeTest::test_corrcoef NumpyOneInputOpsDynamicShapeTest::test_hamming NumpyOneInputOpsDynamicShapeTest::test_hanning -NumpyOneInputOpsDynamicShapeTest::test_isposinf NumpyOneInputOpsDynamicShapeTest::test_isreal NumpyOneInputOpsDynamicShapeTest::test_kaiser NumpyOneInputOpsStaticShapeTest::test_angle NumpyOneInputOpsStaticShapeTest::test_cbrt -NumpyOneInputOpsStaticShapeTest::test_isposinf NumpyOneInputOpsStaticShapeTest::test_isreal NumpyTwoInputOpsDynamicShapeTest::test_gcd NumpyTwoInputOpsDynamicShapeTest::test_hypot diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 100cac2d21e7..8fec3b9c82be 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -2035,22 +2035,9 @@ def stack(x, axis=0): def std(x, axis=None, keepdims=False): - x = get_ov_output(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 - axis = ov_opset.constant(axis, Type.i32).output(0) - # The variance is computed using $Var = E[|x|^2] - |E[x]|^2$, It is faster - # but less numerically stable. - mean = ov_opset.reduce_mean(x, axis, keepdims).output(0) - const_two = ov_opset.constant(2, x.get_element_type()).output(0) - squared_x = ov_opset.power(x, const_two).output(0) - squared_mean = ov_opset.power(mean, const_two).output(0) - squared_x_mean = ov_opset.reduce_mean(squared_x, axis, keepdims) - variance = ov_opset.subtract(squared_x_mean, squared_mean).output(0) - std_var = OpenVINOKerasTensor(ov_opset.sqrt(variance).output(0)) - return std_var + var_x = var(x, axis, keepdims) + std_dev = ov_opset.sqrt(var_x).output(0) + return OpenVINOKerasTensor(std_dev) def swapaxes(x, axis1, axis2): @@ -2415,18 +2402,24 @@ def trapezoid(y, x=None, dx=1.0, axis=-1): def var(x, axis=None, keepdims=False): x = get_ov_output(x) + x_type = x.get_element_type() + x, axis = _resolve_axis(x, axis) + + work_dtype = Type.f64 if x_type.is_integral() else x.get_element_type() + if x_type.is_integral(): + x = ov_opset.convert(x, work_dtype).output(0) 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 - axis = ov_opset.constant(axis, Type.i32).output(0) + zero = ov_opset.constant(0.0, work_dtype).output(0) + return OpenVINOKerasTensor(ov_opset.multiply(x, zero).output(0)) # The variance is computed using $Var = E[|x|^2] - |E[x]|^2$, It is faster # but less numerically stable. mean = ov_opset.reduce_mean(x, axis, keepdims).output(0) - const_two = ov_opset.constant(2, x.get_element_type()).output(0) + const_two = ov_opset.constant(2, work_dtype).output(0) + squared_x = ov_opset.power(x, const_two).output(0) squared_mean = ov_opset.power(mean, const_two).output(0) - squared_x_mean = ov_opset.reduce_mean(squared_x, axis, keepdims) + + squared_x_mean = ov_opset.reduce_mean(squared_x, axis, keepdims).output(0) variance = OpenVINOKerasTensor( ov_opset.subtract(squared_x_mean, squared_mean).output(0) ) From 8536a897892724cec282c3ca56a99e5e8ac44e5f Mon Sep 17 00:00:00 2001 From: "daniel.cahall" Date: Fri, 24 Oct 2025 13:16:28 -0400 Subject: [PATCH 3/8] chore: pydocs --- keras/src/backend/openvino/numpy.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 8fec3b9c82be..78ad25ee3a26 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -554,7 +554,7 @@ def hamming(x): def heaviside(x1, x2): x1 = get_ov_output(x1) - T = x1.get_element_type() + x_type = x1.get_element_type() x2 = get_ov_output(x2, x1.get_element_type()) zero_scalar = ov_opset.constant(0, x1.get_element_type()).output(0) @@ -564,14 +564,13 @@ def heaviside(x1, x2): pos = ov_opset.greater(x1, zero_scalar).output(0) eq = ov_opset.equal(x1, zero_scalar).output(0) - x = ov_opset.select(neg, zero_scalar, x1).output(0) # dtype: x1 dtype - x = ov_opset.select(pos, one_scalar, x).output(0) # dtype: x1 dtype + x = ov_opset.select(neg, zero_scalar, x1).output(0) + x = ov_opset.select(pos, one_scalar, x).output(0) - # 🔧 Make sure x2 matches x1’s dtype before the final select x2_cast = ov_opset.convert(x2, x1.get_element_type()).output(0) x = ov_opset.select(eq, x2_cast, x).output(0) - x = ov_opset.convert(x, T).output(0) + x = ov_opset.convert(x, x_type).output(0) return OpenVINOKerasTensor(x) From 5a2a65d963f2f817ec3f7709b53676ec76481620 Mon Sep 17 00:00:00 2001 From: "daniel.cahall" Date: Fri, 24 Oct 2025 13:16:44 -0400 Subject: [PATCH 4/8] chore: pydocs --- keras/src/backend/openvino/numpy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 78ad25ee3a26..1b0be6b7b796 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -556,7 +556,7 @@ def heaviside(x1, x2): x1 = get_ov_output(x1) x_type = x1.get_element_type() - x2 = get_ov_output(x2, x1.get_element_type()) + x2 = get_ov_output(x2, x_type) zero_scalar = ov_opset.constant(0, x1.get_element_type()).output(0) one_scalar = ov_opset.constant(1, x1.get_element_type()).output(0) From 878dadda7349bfefa6ee289f8f242d97b512a4eb Mon Sep 17 00:00:00 2001 From: "daniel.cahall" Date: Fri, 24 Oct 2025 13:17:07 -0400 Subject: [PATCH 5/8] chore: pydocs --- keras/src/backend/openvino/numpy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 1b0be6b7b796..c2690ed14dfa 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -557,8 +557,8 @@ def heaviside(x1, x2): x_type = x1.get_element_type() x2 = get_ov_output(x2, x_type) - zero_scalar = ov_opset.constant(0, x1.get_element_type()).output(0) - one_scalar = ov_opset.constant(1, x1.get_element_type()).output(0) + zero_scalar = ov_opset.constant(0, x_type).output(0) + one_scalar = ov_opset.constant(1, x_type).output(0) neg = ov_opset.less(x1, zero_scalar).output(0) pos = ov_opset.greater(x1, zero_scalar).output(0) @@ -567,7 +567,7 @@ def heaviside(x1, x2): x = ov_opset.select(neg, zero_scalar, x1).output(0) x = ov_opset.select(pos, one_scalar, x).output(0) - x2_cast = ov_opset.convert(x2, x1.get_element_type()).output(0) + x2_cast = ov_opset.convert(x2, x_type).output(0) x = ov_opset.select(eq, x2_cast, x).output(0) x = ov_opset.convert(x, x_type).output(0) From c3adfcbec6a6a015ed01de48bfa0db680cd146ea Mon Sep 17 00:00:00 2001 From: Danny <33044223+danielenricocahall@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:52:15 -0400 Subject: [PATCH 6/8] Update keras/src/backend/openvino/numpy.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- keras/src/backend/openvino/numpy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index c2690ed14dfa..fb6d32e958fb 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -568,9 +568,9 @@ def heaviside(x1, x2): x = ov_opset.select(pos, one_scalar, x).output(0) x2_cast = ov_opset.convert(x2, x_type).output(0) - x = ov_opset.select(eq, x2_cast, x).output(0) - x = ov_opset.convert(x, x_type).output(0) + if not isinstance(x1, OpenVINOKerasTensor) or not isinstance(x2, OpenVINOKerasTensor): + raise ValueError("Inputs must be OpenVINOKerasTensors") return OpenVINOKerasTensor(x) From d6e5f6fc99dc77240583403aa6ed8419495f93e3 Mon Sep 17 00:00:00 2001 From: "daniel.cahall" Date: Fri, 24 Oct 2025 14:02:06 -0400 Subject: [PATCH 7/8] fix heaviside --- keras/src/backend/openvino/numpy.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index fb6d32e958fb..8cbe4e435425 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -555,8 +555,8 @@ def hamming(x): def heaviside(x1, x2): x1 = get_ov_output(x1) x_type = x1.get_element_type() - x2 = get_ov_output(x2, x_type) + zero_scalar = ov_opset.constant(0, x_type).output(0) one_scalar = ov_opset.constant(1, x_type).output(0) @@ -566,11 +566,7 @@ def heaviside(x1, x2): x = ov_opset.select(neg, zero_scalar, x1).output(0) x = ov_opset.select(pos, one_scalar, x).output(0) - - x2_cast = ov_opset.convert(x2, x_type).output(0) - x = ov_opset.convert(x, x_type).output(0) - if not isinstance(x1, OpenVINOKerasTensor) or not isinstance(x2, OpenVINOKerasTensor): - raise ValueError("Inputs must be OpenVINOKerasTensors") + x = ov_opset.select(eq, x2, x).output(0) return OpenVINOKerasTensor(x) From f55f6a08d0104c242c85c1aa9a63c04215d5041e Mon Sep 17 00:00:00 2001 From: "daniel.cahall" Date: Thu, 30 Oct 2025 19:18:55 -0400 Subject: [PATCH 8/8] use broadcast instead of multiply --- keras/src/backend/openvino/numpy.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 8cbe4e435425..c897c77e79ea 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -2404,8 +2404,10 @@ def var(x, axis=None, keepdims=False): if x_type.is_integral(): x = ov_opset.convert(x, work_dtype).output(0) if axis is None: - zero = ov_opset.constant(0.0, work_dtype).output(0) - return OpenVINOKerasTensor(ov_opset.multiply(x, zero).output(0)) + const_zero = ov_opset.constant(0, dtype=work_dtype).output(0) + return OpenVINOKerasTensor( + ov_opset.broadcast(const_zero, ov_opset.shape_of(x)).output(0) + ) # The variance is computed using $Var = E[|x|^2] - |E[x]|^2$, It is faster # but less numerically stable. mean = ov_opset.reduce_mean(x, axis, keepdims).output(0)