Skip to content

Commit e45fedb

Browse files
Correct implementation for several OpenVINO operations (#21752)
* fix absolute, enable all/any * fix ceil func * enable input/output tests for any/all, fix axis resolutoin * fix sqrt for int32 * fix sqrt and sum, consolidate duplicated logic * fix squeeze * add pos/neg/regular inf * fix nan, add finite support * add a note about why we're not using openvino's existing capabilities for finite/infinite
1 parent e2be4de commit e45fedb

File tree

2 files changed

+84
-69
lines changed

2 files changed

+84
-69
lines changed

keras/src/backend/openvino/excluded_concrete_tests.txt

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
NumPyTestRot90
2-
NumpyDtypeTest::test_absolute_bool
32
NumpyDtypeTest::test_add_
4-
NumpyDtypeTest::test_all
53
NumpyDtypeTest::test_angle
6-
NumpyDtypeTest::test_any
74
NumpyDtypeTest::test_argpartition
85
NumpyDtypeTest::test_array
96
NumpyDtypeTest::test_bartlett
@@ -16,7 +13,6 @@ NumpyDtypeTest::test_hypot
1613
NumpyDtypeTest::test_kaiser
1714
NumpyDtypeTest::test_bitwise
1815
NumpyDtypeTest::test_cbrt
19-
NumpyDtypeTest::test_ceil
2016
NumpyDtypeTest::test_concatenate
2117
NumpyDtypeTest::test_corrcoef
2218
NumpyDtypeTest::test_correlate
@@ -29,11 +25,7 @@ NumpyDtypeTest::test_exp2
2925
NumpyDtypeTest::test_flip
3026
NumpyDtypeTest::test_floor_divide
3127
NumpyDtypeTest::test_inner
32-
NumpyDtypeTest::test_isfinite
3328
NumpyDtypeTest::test_isin
34-
NumpyDtypeTest::test_isinf
35-
NumpyDtypeTest::test_isnan
36-
NumpyDtypeTest::test_isposinf
3729
NumpyDtypeTest::test_isreal
3830
NumpyDtypeTest::test_kron
3931
NumpyDtypeTest::test_lcm
@@ -48,10 +40,8 @@ NumpyDtypeTest::test_roll
4840
NumpyDtypeTest::test_round
4941
NumpyDtypeTest::test_searchsorted
5042
NumpyDtypeTest::test_signbit
51-
NumpyDtypeTest::test_sqrt
5243
NumpyDtypeTest::test_std
5344
NumpyDtypeTest::test_subtract
54-
NumpyDtypeTest::test_sum
5545
NumpyDtypeTest::test_swapaxes
5646
NumpyDtypeTest::test_tensordot_
5747
NumpyDtypeTest::test_tile
@@ -61,12 +51,8 @@ NumpyDtypeTest::test_unravel
6151
NumpyDtypeTest::test_var
6252
NumpyDtypeTest::test_vdot
6353
NumpyDtypeTest::test_vstack
64-
NumpyDtypeTest::test_clip_bool
65-
NumpyDtypeTest::test_square_bool
6654
HistogramTest
67-
NumpyOneInputOpsCorrectnessTest::test_all
6855
NumpyOneInputOpsCorrectnessTest::test_angle
69-
NumpyOneInputOpsCorrectnessTest::test_any
7056
NumpyOneInputOpsCorrectnessTest::test_argpartition
7157
NumpyOneInputOpsCorrectnessTest::test_array
7258
NumpyOneInputOpsCorrectnessTest::test_bartlett
@@ -86,9 +72,6 @@ NumpyOneInputOpsCorrectnessTest::test_exp2
8672
NumpyOneInputOpsCorrectnessTest::test_flip
8773
NumpyOneInputOpsCorrectnessTest::test_floor_divide
8874
NumpyOneInputOpsCorrectnessTest::test_imag
89-
NumpyOneInputOpsCorrectnessTest::test_isfinite
90-
NumpyOneInputOpsCorrectnessTest::test_isinf
91-
NumpyOneInputOpsCorrectnessTest::test_isposinf
9275
NumpyOneInputOpsCorrectnessTest::test_isreal
9376
NumpyOneInputOpsCorrectnessTest::test_logaddexp2
9477
NumpyOneInputOpsCorrectnessTest::test_pad_float16_constant_2
@@ -107,10 +90,7 @@ NumpyOneInputOpsCorrectnessTest::test_select
10790
NumpyOneInputOpsCorrectnessTest::test_signbit
10891
NumpyOneInputOpsCorrectnessTest::test_size
10992
NumpyOneInputOpsCorrectnessTest::test_slogdet
110-
NumpyOneInputOpsCorrectnessTest::test_sqrt_int32
111-
NumpyOneInputOpsCorrectnessTest::test_squeeze
11293
NumpyOneInputOpsCorrectnessTest::test_std
113-
NumpyOneInputOpsCorrectnessTest::test_sum
11494
NumpyOneInputOpsCorrectnessTest::test_swapaxes
11595
NumpyOneInputOpsCorrectnessTest::test_tile
11696
NumpyOneInputOpsCorrectnessTest::test_trace

keras/src/backend/openvino/numpy.py

Lines changed: 84 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ def zeros(shape, dtype=None):
181181

182182
def absolute(x):
183183
x = get_ov_output(x)
184+
x_type = x.get_element_type()
185+
if x_type == Type.boolean:
186+
return OpenVINOKerasTensor(x)
184187
return OpenVINOKerasTensor(ov_opset.absolute(x).output(0))
185188

186189

@@ -191,11 +194,10 @@ def abs(x):
191194

192195
def all(x, axis=None, keepdims=False):
193196
x = get_ov_output(x)
197+
x, axis = _resolve_axis(x, axis)
194198
if axis is None:
195-
flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
196-
x = ov_opset.reshape(x, flatten_shape, False).output(0)
197-
axis = 0
198-
axis = ov_opset.constant(axis, Type.i32).output(0)
199+
return OpenVINOKerasTensor(x)
200+
x = ov_opset.convert(x, Type.boolean).output(0)
199201
return OpenVINOKerasTensor(
200202
ov_opset.reduce_logical_and(x, axis, keepdims).output(0)
201203
)
@@ -207,11 +209,10 @@ def angle(x):
207209

208210
def any(x, axis=None, keepdims=False):
209211
x = get_ov_output(x)
212+
x, axis = _resolve_axis(x, axis)
210213
if axis is None:
211-
flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
212-
x = ov_opset.reshape(x, flatten_shape, False).output(0)
213-
axis = 0
214-
axis = ov_opset.constant(axis, Type.i32).output(0)
214+
return OpenVINOKerasTensor(x)
215+
x = ov_opset.convert(x, Type.boolean).output(0)
215216
return OpenVINOKerasTensor(
216217
ov_opset.reduce_logical_or(x, axis, keepdims).output(0)
217218
)
@@ -256,6 +257,17 @@ def _resolve_axis(x, axis):
256257
return x, axis
257258

258259

260+
def _upcast_type_if_needed(x):
261+
x_type = x.get_element_type()
262+
if x_type == Type.boolean:
263+
x = ov_opset.convert(x, Type.i32).output(0)
264+
elif x_type in (Type.i8, Type.i16):
265+
x = ov_opset.convert(x, Type.i32).output(0)
266+
elif x_type in (Type.u8, Type.u16):
267+
x = ov_opset.convert(x, Type.u32).output(0)
268+
return x
269+
270+
259271
def append(x1, x2, axis=None):
260272
x1, x2 = get_ov_output(x1), get_ov_output(x2)
261273
x1, x2 = _align_operand_types(x1, x2, "append()")
@@ -616,11 +628,18 @@ def cbrt(x):
616628

617629
def ceil(x):
618630
x = get_ov_output(x)
619-
return OpenVINOKerasTensor(ov_opset.ceil(x).output(0))
631+
x_type = x.get_element_type()
632+
if x_type.is_integral():
633+
x = ov_opset.convert(x, OPENVINO_DTYPES[config.floatx()]).output(0)
634+
ceiling = ov_opset.ceil(x).output(0)
635+
return OpenVINOKerasTensor(ceiling)
620636

621637

622638
def clip(x, x_min, x_max):
623639
x = get_ov_output(x)
640+
x_type = x.get_element_type()
641+
if x_type == Type.boolean:
642+
x = ov_opset.convert(x, Type.i32).output(0)
624643
x_min = get_ov_output(x_min, x.get_element_type())
625644
x_max = get_ov_output(x_max, x.get_element_type())
626645
clip_by_min = ov_opset.maximum(x, x_min).output(0)
@@ -1012,25 +1031,52 @@ def isclose(x1, x2, rtol=1e-5, atol=1e-8, equal_nan=False):
10121031

10131032

10141033
def isfinite(x):
1015-
x = get_ov_output(x)
1016-
return OpenVINOKerasTensor(ov_opset.is_finite(x).output(0))
1034+
# NOTE: openvino has an is_finite operation but it does not properly
1035+
# catch np.inf and -np.inf as not finite values. Hence we bootstrap here. If
1036+
# that ever changes, we could simplify this to just call that operation.
1037+
inf_values = get_ov_output(isinf(x))
1038+
nan_values = get_ov_output(isnan(x))
1039+
all_non_finite_values = ov_opset.logical_or(inf_values, nan_values).output(
1040+
0
1041+
)
1042+
is_finite = ov_opset.logical_not(all_non_finite_values).output(0)
1043+
return OpenVINOKerasTensor(is_finite)
10171044

10181045

10191046
def isin(x1, x2, assume_unique=False, invert=False):
10201047
raise NotImplementedError("`isin` is not supported with openvino backend")
10211048

10221049

10231050
def isinf(x):
1024-
x = get_ov_output(x)
1025-
return OpenVINOKerasTensor(ov_opset.is_inf(x).output(0))
1051+
pos_inf = get_ov_output(isposinf(x))
1052+
neg_inf = get_ov_output(isneginf(x))
1053+
inf = ov_opset.logical_or(pos_inf, neg_inf).output(0)
1054+
return OpenVINOKerasTensor(inf)
10261055

10271056

10281057
def isnan(x):
10291058
x = get_ov_output(x)
1059+
x_type = x.get_element_type()
1060+
if x_type.is_integral():
1061+
x = ov_opset.convert(x, OPENVINO_DTYPES[config.floatx()])
10301062
return OpenVINOKerasTensor(ov_opset.is_nan(x).output(0))
10311063

10321064

10331065
def isneginf(x):
1066+
return _is_inf(x, pos=False)
1067+
1068+
1069+
def isposinf(x):
1070+
return _is_inf(x)
1071+
1072+
1073+
def _is_inf(x, pos=True):
1074+
# NOTE: there is an ov_opset.is_inf but it does not catch
1075+
# numpy infinite values like np.inf and -np.inf, hence why we have this
1076+
# if this ever changes in OpenVINO, we can do this instead:
1077+
# ov_opset.is_inf(x, {"detect_positive": pos, "detect_negative": not pos})
1078+
# for each infinite sign
1079+
inf_value = np.inf if pos else -np.inf
10341080
x = get_ov_output(x)
10351081
x_type = x.get_element_type()
10361082

@@ -1043,26 +1089,19 @@ def isneginf(x):
10431089

10441090
if x_type == Type.bf16:
10451091
x_f32 = ov_opset.convert(x, Type.f32).output(0)
1046-
neg_inf = ov_opset.constant(-np.inf, Type.f32).output(0)
1047-
is_neg_inf = ov_opset.equal(x_f32, neg_inf).output(0)
1092+
inf = ov_opset.constant(inf_value, Type.f32).output(0)
1093+
is_inf = ov_opset.equal(x_f32, inf).output(0)
10481094
else:
10491095
if x_type == Type.f16:
1050-
neg_inf = ov_opset.constant(-np.inf, Type.f16).output(0)
1096+
inf = ov_opset.constant(inf_value, Type.f16).output(0)
10511097
elif x_type == Type.f32:
1052-
neg_inf = ov_opset.constant(-np.inf, Type.f32).output(0)
1098+
inf = ov_opset.constant(inf_value, Type.f32).output(0)
10531099
elif x_type == Type.f64:
1054-
neg_inf = ov_opset.constant(-np.inf, Type.f64).output(0)
1100+
inf = ov_opset.constant(inf_value, Type.f64).output(0)
10551101
else:
1056-
neg_inf = ov_opset.constant(-np.inf, Type.f32).output(0)
1057-
is_neg_inf = ov_opset.equal(x, neg_inf).output(0)
1058-
1059-
return OpenVINOKerasTensor(is_neg_inf)
1060-
1061-
1062-
def isposinf(x):
1063-
raise NotImplementedError(
1064-
"`isposinf` is not supported with openvino backend"
1065-
)
1102+
inf = ov_opset.constant(inf_value, Type.f32).output(0)
1103+
is_inf = ov_opset.equal(x, inf).output(0)
1104+
return OpenVINOKerasTensor(is_inf)
10661105

10671106

10681107
def isreal(x):
@@ -1746,23 +1785,10 @@ def prod(x, axis=None, keepdims=False, dtype=None):
17461785
x = ov_opset.convert(x, ov_dtype).output(0)
17471786
# Otherwise, apply dtype promotion rules before reduction.
17481787
else:
1749-
x_type = x.get_element_type()
1750-
if x_type == Type.boolean:
1751-
x = ov_opset.convert(x, Type.i32).output(0)
1752-
elif x_type in (Type.i8, Type.i16):
1753-
x = ov_opset.convert(x, Type.i32).output(0)
1754-
elif x_type in (Type.u8, Type.u16):
1755-
x = ov_opset.convert(x, Type.u32).output(0)
1756-
1788+
x = _upcast_type_if_needed(x)
1789+
x, axis = _resolve_axis(x, axis)
17571790
if axis is None:
1758-
flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
1759-
x = ov_opset.reshape(x, flatten_shape, False).output(0)
1760-
axis = 0
1761-
1762-
if isinstance(axis, tuple):
1763-
axis = list(axis)
1764-
axis = ov_opset.constant(axis, Type.i32).output(0)
1765-
1791+
return OpenVINOKerasTensor(x)
17661792
# Compute the product
17671793
result = ov_opset.reduce_prod(x, axis, keepdims).output(0)
17681794

@@ -2323,12 +2349,19 @@ def negative(x):
23232349

23242350
def square(x):
23252351
x = get_ov_output(x)
2352+
x_type = x.get_element_type()
2353+
if x_type == Type.boolean:
2354+
x = ov_opset.convert(x, Type.i32).output(0)
23262355
const_two = ov_opset.constant(2, x.get_element_type()).output(0)
23272356
return OpenVINOKerasTensor(ov_opset.power(x, const_two).output(0))
23282357

23292358

23302359
def sqrt(x):
23312360
x = get_ov_output(x)
2361+
x_type = x.get_element_type()
2362+
if x_type.is_integral():
2363+
ov_type = OPENVINO_DTYPES[config.floatx()]
2364+
x = ov_opset.convert(x, ov_type).output(0)
23322365
return OpenVINOKerasTensor(ov_opset.sqrt(x).output(0))
23332366

23342367

@@ -2339,6 +2372,8 @@ def squeeze(x, axis=None):
23392372
for idx, dim in enumerate(x.get_partial_shape()):
23402373
if dim == 1:
23412374
axis.append(idx)
2375+
if isinstance(axis, tuple):
2376+
axis = list(axis)
23422377
axis = ov_opset.constant(axis, Type.i32).output(0)
23432378
return OpenVINOKerasTensor(ov_opset.squeeze(x, axis).output(0))
23442379

@@ -2385,12 +2420,12 @@ def var(x, axis=None, keepdims=False):
23852420

23862421
def sum(x, axis=None, keepdims=False):
23872422
x = get_ov_output(x)
2423+
x, axis = _resolve_axis(x, axis)
23882424
if axis is None:
2389-
flatten_shape = ov_opset.constant([-1], Type.i32).output(0)
2390-
x = ov_opset.reshape(x, flatten_shape, False).output(0)
2391-
axis = 0
2392-
axis = ov_opset.constant(axis, Type.i32).output(0)
2393-
return OpenVINOKerasTensor(ov_opset.reduce_sum(x, axis, keepdims).output(0))
2425+
return OpenVINOKerasTensor(x)
2426+
x = _upcast_type_if_needed(x)
2427+
summed_value = ov_opset.reduce_sum(x, axis, keepdims).output(0)
2428+
return OpenVINOKerasTensor(summed_value)
23942429

23952430

23962431
def eye(N, M=None, k=0, dtype=None):

0 commit comments

Comments
 (0)