Skip to content

Commit a5e6d0a

Browse files
Correct implementation for several OpenVINO operations (#21746)
* correct implementation of `floor` and enable testing * correct implementation for mean, min, max + consolidate duplicate logic * correct impl for cumsum with bools * make gemini corrections * remove disabling of initial * typo as i committed because incompetence * fix dynamic shape behavior * fix dynamic shape behavior
1 parent 3c3d6ad commit a5e6d0a

File tree

2 files changed

+95
-76
lines changed

2 files changed

+95
-76
lines changed

keras/src/backend/openvino/excluded_concrete_tests.txt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@ NumpyDtypeTest::test_corrcoef
2222
NumpyDtypeTest::test_correlate
2323
NumpyDtypeTest::test_cross
2424
NumpyDtypeTest::test_cumprod
25-
NumpyDtypeTest::test_cumsum_bool
2625
NumpyDtypeTest::test_diag
2726
NumpyDtypeTest::test_digitize
2827
NumpyDtypeTest::test_einsum
2928
NumpyDtypeTest::test_exp2
3029
NumpyDtypeTest::test_flip
31-
NumpyDtypeTest::test_floor
30+
NumpyDtypeTest::test_floor_divide
3231
NumpyDtypeTest::test_inner
3332
NumpyDtypeTest::test_isfinite
3433
NumpyDtypeTest::test_isin
@@ -40,8 +39,7 @@ NumpyDtypeTest::test_kron
4039
NumpyDtypeTest::test_lcm
4140
NumpyDtypeTest::test_logaddexp2
4241
NumpyDtypeTest::test_matmul_
43-
NumpyDtypeTest::test_max
44-
NumpyDtypeTest::test_mean
42+
NumpyDtypeTest::test_maximum_python_types
4543
NumpyDtypeTest::test_minimum_python_types
4644
NumpyDtypeTest::test_multiply
4745
NumpyDtypeTest::test_power
@@ -93,8 +91,6 @@ NumpyOneInputOpsCorrectnessTest::test_isinf
9391
NumpyOneInputOpsCorrectnessTest::test_isposinf
9492
NumpyOneInputOpsCorrectnessTest::test_isreal
9593
NumpyOneInputOpsCorrectnessTest::test_logaddexp2
96-
NumpyOneInputOpsCorrectnessTest::test_max
97-
NumpyOneInputOpsCorrectnessTest::test_mean
9894
NumpyOneInputOpsCorrectnessTest::test_pad_float16_constant_2
9995
NumpyOneInputOpsCorrectnessTest::test_pad_float32_constant_2
10096
NumpyOneInputOpsCorrectnessTest::test_pad_float64_constant_2

keras/src/backend/openvino/numpy.py

Lines changed: 93 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -76,25 +76,81 @@ def multiply(x1, x2):
7676

7777

7878
def mean(x, axis=None, keepdims=False):
79-
x = get_ov_output(x)
80-
if axis is None:
81-
flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
82-
x = ov_opset.reshape(x, flatten_shape, False).output(0)
83-
axis = 0
84-
axis_const = ov_opset.constant(axis, dtype=Type.i32).output(0)
85-
mean_ops = ov_opset.reduce_mean(x, axis_const, keepdims)
86-
return OpenVINOKerasTensor(mean_ops.output(0))
79+
x_ov = get_ov_output(x)
80+
x_shape = x_ov.get_partial_shape().to_shape()
81+
x_type = x_ov.get_element_type()
82+
83+
was_axis_none = axis is None
84+
x_resolved, axis_resolved = _resolve_axis(x_ov, axis)
85+
86+
if axis_resolved is None:
87+
return OpenVINOKerasTensor(x_ov)
88+
89+
if x_type.is_integral():
90+
ov_type = OPENVINO_DTYPES[config.floatx()]
91+
x_resolved = ov_opset.convert(x_resolved, ov_type).output(0)
92+
93+
result = ov_opset.reduce_mean(x_resolved, axis_resolved, keepdims).output(0)
94+
95+
if keepdims and was_axis_none:
96+
result_shape = [1] * len(x_shape)
97+
result = ov_opset.reshape(
98+
result,
99+
ov_opset.constant(result_shape, Type.i32).output(0),
100+
False,
101+
).output(0)
102+
103+
return OpenVINOKerasTensor(result)
87104

88105

89106
def max(x, axis=None, keepdims=False, initial=None):
90-
assert initial is None, (
91-
"`max` with not None initial is not supported by openvino backend"
92-
)
107+
return _compute_extrema(x, "max", axis, keepdims, initial)
108+
109+
110+
def _compute_extrema(x, operation, axis=None, keepdims=False, initial=None):
111+
if operation == "min":
112+
reduction_op = ov_opset.reduce_min
113+
elementwise_op = ov_opset.minimum
114+
elif operation == "max":
115+
reduction_op = ov_opset.reduce_max
116+
elementwise_op = ov_opset.maximum
117+
else:
118+
raise ValueError(
119+
f"Operation must be 'min' or 'max', received {operation}"
120+
)
121+
93122
x = get_ov_output(x)
94-
reduce_axis = ov_opset.constant(axis, Type.i32).output(0)
95-
return OpenVINOKerasTensor(
96-
ov_opset.reduce_max(x, reduce_axis, keepdims).output(0)
97-
)
123+
x_type = x.get_element_type()
124+
x_for_rank = x
125+
126+
is_bool = x_type == Type.boolean
127+
if is_bool:
128+
x = ov_opset.convert(x, Type.i32).output(0)
129+
x_type = Type.i32
130+
131+
if isinstance(axis, tuple) and len(axis) == 0:
132+
return OpenVINOKerasTensor(x)
133+
134+
was_axis_none = axis is None
135+
x, axis = _resolve_axis(x, axis)
136+
137+
result = reduction_op(x, axis, keepdims).output(0)
138+
139+
if initial is not None:
140+
initial_tensor = ov_opset.constant(initial, x_type).output(0)
141+
result = elementwise_op(result, initial_tensor).output(0)
142+
143+
if keepdims and was_axis_none:
144+
orig_shape = ov_opset.shape_of(x_for_rank, Type.i32).output(0)
145+
orig_rank_shape = ov_opset.shape_of(orig_shape, Type.i32).output(0)
146+
one = ov_opset.constant(1, Type.i32).output(0)
147+
result_shape = ov_opset.broadcast(one, orig_rank_shape).output(0)
148+
result = ov_opset.reshape(result, result_shape, False).output(0)
149+
150+
if is_bool:
151+
result = ov_opset.convert(result, Type.boolean).output(0)
152+
153+
return OpenVINOKerasTensor(result)
98154

99155

100156
def ones(shape, dtype=None):
@@ -162,17 +218,11 @@ def any(x, axis=None, keepdims=False):
162218

163219

164220
def amax(x, axis=None, keepdims=False):
165-
if axis == () or axis == []:
166-
return x
167221
x = get_ov_output(x)
168222
x_type = x.get_element_type()
223+
x, axis = _resolve_axis(x, axis)
169224
if axis is None:
170-
flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
171-
x = ov_opset.reshape(x, flatten_shape, False).output(0)
172-
axis = 0
173-
if isinstance(axis, tuple):
174-
axis = list(axis)
175-
axis = ov_opset.constant(axis, Type.i32).output(0)
225+
return OpenVINOKerasTensor(x)
176226
if x_type == Type.boolean:
177227
return OpenVINOKerasTensor(
178228
ov_opset.reduce_logical_or(x, axis, keepdims).output(0)
@@ -181,22 +231,29 @@ def amax(x, axis=None, keepdims=False):
181231

182232

183233
def amin(x, axis=None, keepdims=False):
184-
if axis == () or axis == []:
185-
return x
186234
x = get_ov_output(x)
187235
x_type = x.get_element_type()
236+
x, axis = _resolve_axis(x, axis)
237+
if axis is None:
238+
return OpenVINOKerasTensor(x)
239+
if x_type == Type.boolean:
240+
return OpenVINOKerasTensor(
241+
ov_opset.reduce_logical_and(x, axis, keepdims).output(0)
242+
)
243+
return OpenVINOKerasTensor(ov_opset.reduce_min(x, axis, keepdims).output(0))
244+
245+
246+
def _resolve_axis(x, axis):
247+
if axis == () or axis == []:
248+
return x, None
188249
if axis is None:
189250
flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
190251
x = ov_opset.reshape(x, flatten_shape, False).output(0)
191252
axis = 0
192253
if isinstance(axis, tuple):
193254
axis = list(axis)
194255
axis = ov_opset.constant(axis, Type.i32).output(0)
195-
if x_type == Type.boolean:
196-
return OpenVINOKerasTensor(
197-
ov_opset.reduce_logical_and(x, axis, keepdims).output(0)
198-
)
199-
return OpenVINOKerasTensor(ov_opset.reduce_min(x, axis, keepdims).output(0))
256+
return x, axis
200257

201258

202259
def append(x1, x2, axis=None):
@@ -651,6 +708,8 @@ def cumsum(x, axis=None, dtype=None):
651708
x = ov_opset.reshape(x, flatten_shape, False).output(0)
652709
axis = 0
653710
axis = ov_opset.constant(axis, Type.i32).output(0)
711+
if x.get_element_type() == Type.boolean:
712+
x = ov_opset.convert(x, Type.i32).output(0)
654713
return OpenVINOKerasTensor(ov_opset.cumsum(x, axis).output(0))
655714

656715

@@ -838,6 +897,9 @@ def flip(x, axis=None):
838897

839898
def floor(x):
840899
x = get_ov_output(x)
900+
x_type = x.get_element_type()
901+
if x_type.is_integral():
902+
x = ov_opset.convert(x, OPENVINO_DTYPES[config.floatx()])
841903
return OpenVINOKerasTensor(ov_opset.floor(x).output(0))
842904

843905

@@ -1509,46 +1571,7 @@ def meshgrid(*x, indexing="xy"):
15091571

15101572

15111573
def min(x, axis=None, keepdims=False, initial=None):
1512-
x = get_ov_output(x)
1513-
original_type = x.get_element_type()
1514-
x_type = original_type
1515-
x_shape = x.get_partial_shape().to_shape()
1516-
1517-
is_bool = x_type == Type.boolean
1518-
if is_bool:
1519-
x = ov_opset.convert(x, Type.i32).output(0)
1520-
x_type = Type.i32
1521-
1522-
if isinstance(axis, tuple) and len(axis) == 0:
1523-
return OpenVINOKerasTensor(x)
1524-
1525-
if axis is None:
1526-
flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
1527-
x = ov_opset.reshape(x, flatten_shape, False).output(0)
1528-
axis = 0
1529-
1530-
if isinstance(axis, tuple):
1531-
axis = list(axis)
1532-
1533-
axis_const = ov_opset.constant(axis, Type.i32).output(0)
1534-
min_result = ov_opset.reduce_min(x, axis_const, keepdims).output(0)
1535-
1536-
if initial is not None:
1537-
initial_tensor = ov_opset.constant(initial, x_type).output(0)
1538-
min_result = ov_opset.minimum(min_result, initial_tensor).output(0)
1539-
1540-
if keepdims:
1541-
result_shape = [1] * len(x_shape)
1542-
min_result = ov_opset.reshape(
1543-
min_result,
1544-
ov_opset.constant(result_shape, Type.i32).output(0),
1545-
False,
1546-
).output(0)
1547-
1548-
if is_bool:
1549-
min_result = ov_opset.convert(min_result, Type.boolean).output(0)
1550-
1551-
return OpenVINOKerasTensor(min_result)
1574+
return _compute_extrema(x, "min", axis, keepdims, initial)
15521575

15531576

15541577
def minimum(x1, x2):

0 commit comments

Comments
 (0)