Skip to content

Commit 94ca6ef

Browse files
Add linspace and logspace implementations in OpenVINO NumPy backend (#21613)
* Added logspace and linespace numpy implemation * Minor changes * Added Changes suggested by BOT * Minor changes * Minor changes * Minor changes * Minor Changes * Minor Changes * Performed the Suggested Changes * Performed the Suggested Changes --------- Co-authored-by: hertschuh <[email protected]>
1 parent be971be commit 94ca6ef

File tree

2 files changed

+146
-8
lines changed

2 files changed

+146
-8
lines changed

keras/src/backend/openvino/excluded_concrete_tests.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@ NumpyDtypeTest::test_isnan
3939
NumpyDtypeTest::test_isposinf
4040
NumpyDtypeTest::test_kron
4141
NumpyDtypeTest::test_lcm
42-
NumpyDtypeTest::test_linspace
4342
NumpyDtypeTest::test_logaddexp2
44-
NumpyDtypeTest::test_logspace
4543
NumpyDtypeTest::test_matmul_
4644
NumpyDtypeTest::test_max
4745
NumpyDtypeTest::test_mean
@@ -142,8 +140,6 @@ NumpyTwoInputOpsCorrectnessTest::test_inner
142140
NumpyTwoInputOpsCorrectnessTest::test_isin
143141
NumpyTwoInputOpsCorrectnessTest::test_kron
144142
NumpyTwoInputOpsCorrectnessTest::test_lcm
145-
NumpyTwoInputOpsCorrectnessTest::test_linspace
146-
NumpyTwoInputOpsCorrectnessTest::test_logspace
147143
NumpyTwoInputOpsCorrectnessTest::test_quantile
148144
NumpyTwoInputOpsCorrectnessTest::test_tensordot
149145
NumpyTwoInputOpsCorrectnessTest::test_vdot

keras/src/backend/openvino/numpy.py

Lines changed: 146 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,9 +1038,131 @@ def less_equal(x1, x2):
10381038
def linspace(
10391039
start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0
10401040
):
1041-
raise NotImplementedError(
1042-
"`linspace` is not supported with openvino backend"
1041+
"""Return evenly spaced numbers over a specified interval.
1042+
1043+
Supports axis=0 (prepend) and axis=-1 (append). Intermediate axis values are
1044+
treated as axis=-1.
1045+
1046+
If `retstep` is True, also returns the step size between values.
1047+
1048+
"""
1049+
1050+
start = get_ov_output(start)
1051+
stop = get_ov_output(stop)
1052+
1053+
if hasattr(num, "output") or isinstance(num, OpenVINOKerasTensor):
1054+
num_tensor = get_ov_output(num)
1055+
try:
1056+
if num_tensor.get_node().get_type_name() == "Constant":
1057+
num_value = num_tensor.get_node().get_vector()[0]
1058+
num = int(num_value)
1059+
else:
1060+
raise NotImplementedError(
1061+
"Dynamic num values not fully supported"
1062+
)
1063+
except Exception as e:
1064+
raise NotImplementedError(
1065+
"Could not extract num value from tensor"
1066+
) from e
1067+
else:
1068+
num = int(num)
1069+
1070+
if dtype is None:
1071+
output_type = OPENVINO_DTYPES[config.floatx()]
1072+
else:
1073+
output_type = OPENVINO_DTYPES[dtype]
1074+
1075+
start = ov_opset.convert(start, output_type).output(0)
1076+
stop = ov_opset.convert(stop, output_type).output(0)
1077+
1078+
if num < 0:
1079+
raise ValueError("Number of samples, `num`, must be non-negative.")
1080+
1081+
if num == 0:
1082+
empty_shape = ov_opset.constant([0], Type.i32).output(0)
1083+
result = ov_opset.broadcast(
1084+
ov_opset.constant(0.0, output_type).output(0), empty_shape
1085+
).output(0)
1086+
if retstep:
1087+
nan_step = ov_opset.constant(np.nan, output_type).output(0)
1088+
return OpenVINOKerasTensor(result), OpenVINOKerasTensor(nan_step)
1089+
return OpenVINOKerasTensor(result)
1090+
1091+
if num == 1:
1092+
result_val = start
1093+
axis_const = ov_opset.constant([axis], Type.i32).output(0)
1094+
result = ov_opset.unsqueeze(result_val, axis_const).output(0)
1095+
if retstep:
1096+
if endpoint:
1097+
step = ov_opset.constant(np.nan, output_type).output(0)
1098+
else:
1099+
step = ov_opset.subtract(stop, start).output(0)
1100+
return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step)
1101+
zero_i32 = ov_opset.constant(0, Type.i32).output(0)
1102+
one_i32 = ov_opset.constant(1, Type.i32).output(0)
1103+
one_i32_array = ov_opset.constant([1], Type.i32).output(0)
1104+
1105+
num_const = ov_opset.constant(num, output_type).output(0)
1106+
1107+
if endpoint:
1108+
divisor = ov_opset.subtract(
1109+
num_const, ov_opset.constant(1, output_type).output(0)
1110+
).output(0)
1111+
else:
1112+
divisor = num_const
1113+
1114+
step = ov_opset.divide(
1115+
ov_opset.subtract(stop, start).output(0), divisor
1116+
).output(0)
1117+
1118+
indices = ov_opset.range(
1119+
zero_i32,
1120+
ov_opset.constant(num, Type.i32).output(0),
1121+
one_i32,
1122+
output_type,
1123+
).output(0)
1124+
1125+
start_shape = ov_opset.convert(
1126+
ov_opset.shape_of(start).output(0), Type.i32
1127+
).output(0)
1128+
indices_shape = ov_opset.convert(
1129+
ov_opset.shape_of(indices).output(0), Type.i32
1130+
).output(0)
1131+
1132+
start_rank = ov_opset.shape_of(start_shape).output(0)
1133+
ones_for_start = ov_opset.broadcast(one_i32, start_rank).output(0)
1134+
1135+
if axis == 0:
1136+
indices_target_shape = ov_opset.concat(
1137+
[indices_shape, ones_for_start], 0
1138+
).output(0)
1139+
start_target_shape = ov_opset.concat(
1140+
[one_i32_array, start_shape], 0
1141+
).output(0)
1142+
else:
1143+
indices_target_shape = ov_opset.concat(
1144+
[ones_for_start, indices_shape], 0
1145+
).output(0)
1146+
start_target_shape = ov_opset.concat(
1147+
[start_shape, one_i32_array], 0
1148+
).output(0)
1149+
1150+
indices_reshaped = ov_opset.reshape(
1151+
indices, indices_target_shape, False
1152+
).output(0)
1153+
start_reshaped = ov_opset.reshape(start, start_target_shape, False).output(
1154+
0
1155+
)
1156+
step_reshaped = ov_opset.reshape(step, start_target_shape, False).output(0)
1157+
1158+
scaled_indices = ov_opset.multiply(indices_reshaped, step_reshaped).output(
1159+
0
10431160
)
1161+
result = ov_opset.add(start_reshaped, scaled_indices).output(0)
1162+
1163+
if retstep:
1164+
return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step)
1165+
return OpenVINOKerasTensor(result)
10441166

10451167

10461168
def log(x):
@@ -1171,10 +1293,30 @@ def logical_or(x1, x2):
11711293

11721294

11731295
def logspace(start, stop, num=50, endpoint=True, base=10, dtype=None, axis=0):
1174-
raise NotImplementedError(
1175-
"`logspace` is not supported with openvino backend"
1296+
linear_samples = linspace(
1297+
start=start,
1298+
stop=stop,
1299+
num=num,
1300+
endpoint=endpoint,
1301+
retstep=False,
1302+
dtype=dtype,
1303+
axis=axis,
11761304
)
11771305

1306+
if dtype is None:
1307+
output_type = OPENVINO_DTYPES[config.floatx()]
1308+
else:
1309+
output_type = OPENVINO_DTYPES[dtype]
1310+
1311+
linear_output = get_ov_output(linear_samples)
1312+
base_tensor = get_ov_output(base)
1313+
1314+
base_tensor = ov_opset.convert(base_tensor, output_type).output(0)
1315+
1316+
result = ov_opset.power(base_tensor, linear_output).output(0)
1317+
1318+
return OpenVINOKerasTensor(result)
1319+
11781320

11791321
def maximum(x1, x2):
11801322
x1 = get_ov_output(x1)

0 commit comments

Comments
 (0)