From 99aa92496944a41ef829d44028599e9502921b38 Mon Sep 17 00:00:00 2001 From: Tuan Vo Date: Sun, 6 Apr 2025 18:18:32 +0700 Subject: [PATCH 01/12] Add linspace decomposition for OpenVINO opset --- .../openvino/excluded_concrete_tests.txt | 2 - keras/src/ops/numpy.py | 131 ++++++++++++------ pytest.ini | 3 + 3 files changed, 93 insertions(+), 43 deletions(-) create mode 100644 pytest.ini diff --git a/keras/src/backend/openvino/excluded_concrete_tests.txt b/keras/src/backend/openvino/excluded_concrete_tests.txt index 3000452394c5..a35d68bc6230 100644 --- a/keras/src/backend/openvino/excluded_concrete_tests.txt +++ b/keras/src/backend/openvino/excluded_concrete_tests.txt @@ -32,7 +32,6 @@ NumpyDtypeTest::test_isclose NumpyDtypeTest::test_isfinite NumpyDtypeTest::test_isinf NumpyDtypeTest::test_isnan -NumpyDtypeTest::test_linspace NumpyDtypeTest::test_log1p NumpyDtypeTest::test_logaddexp NumpyDtypeTest::test_logspace @@ -158,7 +157,6 @@ NumpyTwoInputOpsCorrectnessTest::test_divide_no_nan NumpyTwoInputOpsCorrectnessTest::test_einsum NumpyTwoInputOpsCorrectnessTest::test_inner NumpyTwoInputOpsCorrectnessTest::test_isclose -NumpyTwoInputOpsCorrectnessTest::test_linspace NumpyTwoInputOpsCorrectnessTest::test_logspace NumpyTwoInputOpsCorrectnessTest::test_outer NumpyTwoInputOpsCorrectnessTest::test_quantile diff --git a/keras/src/ops/numpy.py b/keras/src/ops/numpy.py index 880ce930b5b5..5a855840ca3b 100644 --- a/keras/src/ops/numpy.py +++ b/keras/src/ops/numpy.py @@ -3468,51 +3468,100 @@ def compute_output_spec(self, start, stop): @keras_export(["keras.ops.linspace", "keras.ops.numpy.linspace"]) -def linspace( - start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0 -): - """Return evenly spaced numbers over a specified interval. +def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0, *, device=None): + if not isinstance(num, int): + raise TypeError("num must be an integer") + if num < 0: + raise ValueError("num must be non-negative") - Returns `num` evenly spaced samples, calculated over the interval - `[start, stop]`. + if isinstance(start, KerasTensor) or isinstance(stop, KerasTensor): + start_shape = start.shape if isinstance(start, KerasTensor) else backend.shape(start) + stop_shape = stop.shape if isinstance(stop, KerasTensor) else backend.shape(stop) - The endpoint of the interval can optionally be excluded. + try: + base_shape = np.broadcast_shapes(start_shape, stop_shape) + except ValueError: + raise ValueError("`start` and `stop` shapes are incompatible for broadcasting.") - Args: - start: The starting value of the sequence. - stop: The end value of the sequence, unless `endpoint` is set to - `False`. In that case, the sequence consists of all but the last - of `num + 1` evenly spaced samples, so that `stop` is excluded. - Note that the step size changes when `endpoint` is `False`. - num: Number of samples to generate. Defaults to `50`. Must be - non-negative. - endpoint: If `True`, `stop` is the last sample. Otherwise, it is - not included. Defaults to `True`. - retstep: If `True`, return `(samples, step)`, where `step` is the - spacing between samples. - dtype: The type of the output tensor. - axis: The axis in the result to store the samples. Relevant only if - start or stop are array-like. Defaults to `0`. - - Note: - Torch backend does not support `axis` argument. - - Returns: - A tensor of evenly spaced numbers. - If `retstep` is `True`, returns `(samples, step)` - """ - if any_symbolic_tensors((start, stop)): - return Linspace(num, endpoint, retstep, dtype, axis)(start, stop) - return backend.numpy.linspace( - start, - stop, - num=num, - endpoint=endpoint, - retstep=retstep, - dtype=dtype, - axis=axis, - ) + output_shape = list(base_shape) + axis_adj = axis if axis >= 0 else len(output_shape) + axis + 1 + output_shape.insert(axis_adj, num) + if dtype is None: + dtype = backend.floatx() + else: + dtype = backend.standardize_dtype(dtype) + + result = KerasTensor(shape=tuple(output_shape), dtype=dtype) + if retstep: + step_shape = base_shape + step = KerasTensor(shape=tuple(step_shape), dtype=dtype) + return result, step + return result + + start = backend.convert_to_tensor(start) + stop = backend.convert_to_tensor(stop) + + computation_dtype = backend.floatx() + start = backend.cast(start, computation_dtype) + stop = backend.cast(stop, computation_dtype) + + if dtype is None: + output_dtype = computation_dtype + else: + output_dtype = backend.standardize_dtype(dtype) + + if num == 0: + base_shape = backend.shape(start) + output_shape = list(base_shape) + axis_adj = axis if axis >= 0 else len(base_shape) + axis + 1 + output_shape.insert(axis_adj, 0) + result = backend.numpy.zeros(output_shape, dtype=output_dtype) + if retstep: + step = stop - start + return result, step + return result + elif num == 1: + result = backend.numpy.expand_dims(start, axis=axis) + result = backend.cast(result, output_dtype) + if retstep: + step = stop - start + return result, step + return result + + div = num - 1 if endpoint else num + step = (stop - start) / backend.cast(div, computation_dtype) + + indices = backend.numpy.arange(num, dtype=computation_dtype) + + if backend.shape(start) == () and backend.shape(stop) == (): + result = start + indices * step + else: + start_np, stop_np = np.broadcast_arrays(start.numpy(), stop.numpy()) + start = backend.convert_to_tensor(start_np, dtype=computation_dtype) + stop = backend.convert_to_tensor(stop_np, dtype=computation_dtype) + base_shape = backend.shape(start) + output_shape = list(base_shape) + axis_adj = axis if axis >= 0 else len(base_shape) + axis + 1 + output_shape.insert(axis_adj, num) + + indices_shape = [1] * len(output_shape) + indices_shape[axis_adj] = num + indices = backend.numpy.reshape(indices, indices_shape) + indices = backend.numpy.broadcast_to(indices, output_shape) + + start = backend.numpy.expand_dims(start, axis=axis_adj) + start = backend.numpy.broadcast_to(start, output_shape) + step = backend.numpy.expand_dims(step, axis=axis_adj) + step = backend.numpy.broadcast_to(step, output_shape) + + result = start + indices * step + + result = backend.cast(result, output_dtype) + + if retstep: + return result, step + return result class Log(Operation): def call(self, x): diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000000..83635a5b7b9b --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +env = + KERAS_BACKEND=openvino \ No newline at end of file From 39db62cd69d8e6126f6d711542a5ad0a64eab9c7 Mon Sep 17 00:00:00 2001 From: Tuan Vo Date: Tue, 15 Apr 2025 12:09:27 +0700 Subject: [PATCH 02/12] deleted pytest.ini and updated np.linspace using op_opset --- keras/src/backend/openvino/numpy.py | 33 +++++-- keras/src/ops/numpy.py | 131 +++++++++------------------- pytest.ini | 3 - 3 files changed, 68 insertions(+), 99 deletions(-) delete mode 100644 pytest.ini diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index bc0276d4373f..741a8f2352f6 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -821,13 +821,34 @@ def less_equal(x1, x2): x1, x2 = _align_operand_types(x1, x2, "less_equal()") return OpenVINOKerasTensor(ov_opset.less_equal(x1, x2).output(0)) +def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0): + if not isinstance(num, int): + raise TypeError("num must be an integer") + if num < 0: + raise ValueError("num must be non-negative") -def linspace( - start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0 -): - raise NotImplementedError( - "`linspace` is not supported with openvino backend" - ) + start = get_ov_output(start) + stop = get_ov_output(stop) + + if dtype is None: + dtype = OPENVINO_DTYPES[config.floatx()] + + div = num - 1 if endpoint else num + step = (stop - start) / backend.cast(div, dtype) + + indices = ov_opset.arange(num, dtype=dtype) + + result = start + indices * step + + if endpoint and num > 1: + result = ov_opset.scatter(result, ov_opset.constant(num - 1, dtype=Type.i32), stop) + + if axis != 0: + result = ov_opset.unsqueeze(result, ov_opset.constant(axis, Type.i32)) + + if retstep: + return result, step + return result def log(x): diff --git a/keras/src/ops/numpy.py b/keras/src/ops/numpy.py index 5a855840ca3b..880ce930b5b5 100644 --- a/keras/src/ops/numpy.py +++ b/keras/src/ops/numpy.py @@ -3468,100 +3468,51 @@ def compute_output_spec(self, start, stop): @keras_export(["keras.ops.linspace", "keras.ops.numpy.linspace"]) -def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0, *, device=None): - if not isinstance(num, int): - raise TypeError("num must be an integer") - if num < 0: - raise ValueError("num must be non-negative") +def linspace( + start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0 +): + """Return evenly spaced numbers over a specified interval. - if isinstance(start, KerasTensor) or isinstance(stop, KerasTensor): - start_shape = start.shape if isinstance(start, KerasTensor) else backend.shape(start) - stop_shape = stop.shape if isinstance(stop, KerasTensor) else backend.shape(stop) + Returns `num` evenly spaced samples, calculated over the interval + `[start, stop]`. - try: - base_shape = np.broadcast_shapes(start_shape, stop_shape) - except ValueError: - raise ValueError("`start` and `stop` shapes are incompatible for broadcasting.") + The endpoint of the interval can optionally be excluded. - output_shape = list(base_shape) - axis_adj = axis if axis >= 0 else len(output_shape) + axis + 1 - output_shape.insert(axis_adj, num) + Args: + start: The starting value of the sequence. + stop: The end value of the sequence, unless `endpoint` is set to + `False`. In that case, the sequence consists of all but the last + of `num + 1` evenly spaced samples, so that `stop` is excluded. + Note that the step size changes when `endpoint` is `False`. + num: Number of samples to generate. Defaults to `50`. Must be + non-negative. + endpoint: If `True`, `stop` is the last sample. Otherwise, it is + not included. Defaults to `True`. + retstep: If `True`, return `(samples, step)`, where `step` is the + spacing between samples. + dtype: The type of the output tensor. + axis: The axis in the result to store the samples. Relevant only if + start or stop are array-like. Defaults to `0`. + + Note: + Torch backend does not support `axis` argument. + + Returns: + A tensor of evenly spaced numbers. + If `retstep` is `True`, returns `(samples, step)` + """ + if any_symbolic_tensors((start, stop)): + return Linspace(num, endpoint, retstep, dtype, axis)(start, stop) + return backend.numpy.linspace( + start, + stop, + num=num, + endpoint=endpoint, + retstep=retstep, + dtype=dtype, + axis=axis, + ) - if dtype is None: - dtype = backend.floatx() - else: - dtype = backend.standardize_dtype(dtype) - - result = KerasTensor(shape=tuple(output_shape), dtype=dtype) - if retstep: - step_shape = base_shape - step = KerasTensor(shape=tuple(step_shape), dtype=dtype) - return result, step - return result - - start = backend.convert_to_tensor(start) - stop = backend.convert_to_tensor(stop) - - computation_dtype = backend.floatx() - start = backend.cast(start, computation_dtype) - stop = backend.cast(stop, computation_dtype) - - if dtype is None: - output_dtype = computation_dtype - else: - output_dtype = backend.standardize_dtype(dtype) - - if num == 0: - base_shape = backend.shape(start) - output_shape = list(base_shape) - axis_adj = axis if axis >= 0 else len(base_shape) + axis + 1 - output_shape.insert(axis_adj, 0) - result = backend.numpy.zeros(output_shape, dtype=output_dtype) - if retstep: - step = stop - start - return result, step - return result - elif num == 1: - result = backend.numpy.expand_dims(start, axis=axis) - result = backend.cast(result, output_dtype) - if retstep: - step = stop - start - return result, step - return result - - div = num - 1 if endpoint else num - step = (stop - start) / backend.cast(div, computation_dtype) - - indices = backend.numpy.arange(num, dtype=computation_dtype) - - if backend.shape(start) == () and backend.shape(stop) == (): - result = start + indices * step - else: - start_np, stop_np = np.broadcast_arrays(start.numpy(), stop.numpy()) - start = backend.convert_to_tensor(start_np, dtype=computation_dtype) - stop = backend.convert_to_tensor(stop_np, dtype=computation_dtype) - base_shape = backend.shape(start) - output_shape = list(base_shape) - axis_adj = axis if axis >= 0 else len(base_shape) + axis + 1 - output_shape.insert(axis_adj, num) - - indices_shape = [1] * len(output_shape) - indices_shape[axis_adj] = num - indices = backend.numpy.reshape(indices, indices_shape) - indices = backend.numpy.broadcast_to(indices, output_shape) - - start = backend.numpy.expand_dims(start, axis=axis_adj) - start = backend.numpy.broadcast_to(start, output_shape) - step = backend.numpy.expand_dims(step, axis=axis_adj) - step = backend.numpy.broadcast_to(step, output_shape) - - result = start + indices * step - - result = backend.cast(result, output_dtype) - - if retstep: - return result, step - return result class Log(Operation): def call(self, x): diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 83635a5b7b9b..000000000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -env = - KERAS_BACKEND=openvino \ No newline at end of file From 19dbfffce22bc1909eeaeb22bbd0987b28d3fae3 Mon Sep 17 00:00:00 2001 From: Tuan Vo Date: Tue, 15 Apr 2025 12:23:02 +0700 Subject: [PATCH 03/12] minor fix for linspace --- 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 741a8f2352f6..c3986710b342 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -834,7 +834,7 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis dtype = OPENVINO_DTYPES[config.floatx()] div = num - 1 if endpoint else num - step = (stop - start) / backend.cast(div, dtype) + step = (stop - start) / ov_opset.convert(div, dtype) indices = ov_opset.arange(num, dtype=dtype) From ebcd971f995a2881f50665d135dec322101a601f Mon Sep 17 00:00:00 2001 From: Tuan Vo Date: Tue, 22 Apr 2025 23:58:03 +0700 Subject: [PATCH 04/12] Fix np.linspace implemetation using OpenVINO opset --- keras/src/backend/openvino/numpy.py | 68 +++++++++++++++++------------ 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 452f462a72f4..92ccf09a1cc4 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -906,33 +906,47 @@ def less_equal(x1, x2): x1, x2 = _align_operand_types(x1, x2, "less_equal()") return OpenVINOKerasTensor(ov_opset.less_equal(x1, x2).output(0)) -def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0): - if not isinstance(num, int): - raise TypeError("num must be an integer") - if num < 0: - raise ValueError("num must be non-negative") - - start = get_ov_output(start) - stop = get_ov_output(stop) - - if dtype is None: - dtype = OPENVINO_DTYPES[config.floatx()] - - div = num - 1 if endpoint else num - step = (stop - start) / ov_opset.convert(div, dtype) - - indices = ov_opset.arange(num, dtype=dtype) - - result = start + indices * step - - if endpoint and num > 1: - result = ov_opset.scatter(result, ov_opset.constant(num - 1, dtype=Type.i32), stop) - - if axis != 0: - result = ov_opset.unsqueeze(result, ov_opset.constant(axis, Type.i32)) - - if retstep: - return result, step +def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0): + if not isinstance(num, int): + raise TypeError("num must be an integer") + if num < 0: + raise ValueError("num must be non-negative") + + start = get_ov_output(start) + stop = get_ov_output(stop) + + if dtype is None: + dtype = OPENVINO_DTYPES[config.floatx()] + + # Convert inputs to specified dtype + start = ov_opset.convert(start, dtype) + stop = ov_opset.convert(stop, dtype) + + div = num - 1 if endpoint else num + div_const = ov_opset.constant(div, dtype=dtype) + + delta = ov_opset.subtract(stop, start) + step = ov_opset.divide(delta, div_const) + + indices = ov_opset.range( + ov_opset.constant(0, dtype=dtype), + ov_opset.constant(num, dtype=dtype), + ov_opset.constant(1, dtype=dtype) + ) + + scaled_indices = ov_opset.multiply(indices, step) + result = ov_opset.add(start, scaled_indices) + + if endpoint and num > 1: + last_idx = ov_opset.constant(num - 1, dtype=Type.i32) + result = ov_opset.scatter_element_update(result, last_idx, stop) + + if axis != 0: + axis_const = ov_opset.constant([axis], dtype=Type.i64) + result = ov_opset.unsqueeze(result, axis_const) + + if retstep: + return result, step return result From 10ce0e8623ca16233932220db21982c8fda7f1ed Mon Sep 17 00:00:00 2001 From: Tuan Vo Date: Wed, 23 Apr 2025 00:56:53 +0700 Subject: [PATCH 05/12] Fix np.linspace implementation using OpenVINO opset --- keras/src/backend/openvino/numpy.py | 44 ++++++++++++++++------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 92ccf09a1cc4..5989a3765394 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -906,6 +906,7 @@ def less_equal(x1, x2): x1, x2 = _align_operand_types(x1, x2, "less_equal()") return OpenVINOKerasTensor(ov_opset.less_equal(x1, x2).output(0)) + def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0): if not isinstance(num, int): raise TypeError("num must be an integer") @@ -916,38 +917,41 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis stop = get_ov_output(stop) if dtype is None: - dtype = OPENVINO_DTYPES[config.floatx()] + ov_dtype = OPENVINO_DTYPES[config.floatx()] + else: + ov_dtype = OPENVINO_DTYPES[dtype] - # Convert inputs to specified dtype - start = ov_opset.convert(start, dtype) - stop = ov_opset.convert(stop, dtype) + start = ov_opset.convert(start, ov_dtype).output(0) + stop = ov_opset.convert(stop, ov_dtype).output(0) div = num - 1 if endpoint else num - div_const = ov_opset.constant(div, dtype=dtype) - - delta = ov_opset.subtract(stop, start) - step = ov_opset.divide(delta, div_const) + div_const = ov_opset.constant(div, ov_dtype).output(0) + delta = ov_opset.subtract(stop, start).output(0) + step = ov_opset.divide(delta, div_const).output(0) + dtype_str = str(ov_dtype).split('.')[-1] + indices = ov_opset.range( - ov_opset.constant(0, dtype=dtype), - ov_opset.constant(num, dtype=dtype), - ov_opset.constant(1, dtype=dtype) - ) + ov_opset.constant(0, Type.i32).output(0), + ov_opset.constant(num, Type.i32).output(0), + ov_opset.constant(1, Type.i32).output(0), + dtype_str + ).output(0) - scaled_indices = ov_opset.multiply(indices, step) - result = ov_opset.add(start, scaled_indices) + scaled_indices = ov_opset.multiply(indices, step).output(0) + result = ov_opset.add(start, scaled_indices).output(0) if endpoint and num > 1: - last_idx = ov_opset.constant(num - 1, dtype=Type.i32) - result = ov_opset.scatter_element_update(result, last_idx, stop) + last_idx = ov_opset.constant(num - 1, Type.i32).output(0) + result = ov_opset.scatter_element_update(result, last_idx, stop).output(0) if axis != 0: - axis_const = ov_opset.constant([axis], dtype=Type.i64) - result = ov_opset.unsqueeze(result, axis_const) + axis_const = ov_opset.constant([axis], Type.i64).output(0) + result = ov_opset.unsqueeze(result, axis_const).output(0) if retstep: - return result, step - return result + return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step) + return OpenVINOKerasTensor(result) def log(x): From c95b8328cbb1f2231dc328da41b85c9adffe87be Mon Sep 17 00:00:00 2001 From: Tuan Vo Date: Wed, 23 Apr 2025 03:03:30 +0700 Subject: [PATCH 06/12] fix np.linspace using openvino opset --- keras/src/backend/openvino/numpy.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 5989a3765394..f5de8d4cd8af 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -929,13 +929,28 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis delta = ov_opset.subtract(stop, start).output(0) step = ov_opset.divide(delta, div_const).output(0) - dtype_str = str(ov_dtype).split('.')[-1] + type_to_str = { + Type.f16: "f16", + Type.f32: "f32", + Type.f64: "f64", + Type.bf16: "bf16", + Type.i8: "i8", + Type.i16: "i16", + Type.i32: "i32", + Type.i64: "i64", + Type.u8: "u8", + Type.u16: "u16", + Type.u32: "u32", + Type.u64: "u64" + } + + type_str = type_to_str.get(ov_dtype, "f32") indices = ov_opset.range( ov_opset.constant(0, Type.i32).output(0), ov_opset.constant(num, Type.i32).output(0), ov_opset.constant(1, Type.i32).output(0), - dtype_str + type_str ).output(0) scaled_indices = ov_opset.multiply(indices, step).output(0) From d083981673fc12fac7aef9515042635c5528c4ac Mon Sep 17 00:00:00 2001 From: Tuan Vo Date: Wed, 23 Apr 2025 03:21:04 +0700 Subject: [PATCH 07/12] fixed shape and incorrect function name --- keras/src/backend/openvino/numpy.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index f5de8d4cd8af..9ab8db2fcabe 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -924,6 +924,11 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis start = ov_opset.convert(start, ov_dtype).output(0) stop = ov_opset.convert(stop, ov_dtype).output(0) + # Check if we're dealing with arrays + start_shape = start.get_shape() + stop_shape = stop.get_shape() + is_array_input = len(start_shape) > 0 or len(stop_shape) > 0 + div = num - 1 if endpoint else num div_const = ov_opset.constant(div, ov_dtype).output(0) delta = ov_opset.subtract(stop, start).output(0) @@ -944,21 +949,31 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis Type.u64: "u64" } - type_str = type_to_str.get(ov_dtype, "f32") + type_str = type_to_str.get(ov_dtype, "f32") indices = ov_opset.range( ov_opset.constant(0, Type.i32).output(0), ov_opset.constant(num, Type.i32).output(0), ov_opset.constant(1, Type.i32).output(0), - type_str + type_str ).output(0) + if is_array_input: + # For array inputs, we need to reshape the indices + # to properly broadcast with multidimensional steps + new_shape = [] + new_shape.append(num) + for _ in range(len(start_shape)): + new_shape.append(1) + indices = ov_opset.reshape(indices, ov_opset.constant(new_shape, Type.i64).output(0)).output(0) + scaled_indices = ov_opset.multiply(indices, step).output(0) result = ov_opset.add(start, scaled_indices).output(0) if endpoint and num > 1: last_idx = ov_opset.constant(num - 1, Type.i32).output(0) - result = ov_opset.scatter_element_update(result, last_idx, stop).output(0) + # Fix 1: Use the correct function name + result = ov_opset.scatter_elements_update(result, last_idx, stop).output(0) if axis != 0: axis_const = ov_opset.constant([axis], Type.i64).output(0) @@ -966,9 +981,7 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis if retstep: return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step) - return OpenVINOKerasTensor(result) - - + return OpenVINOKerasTensor(result) def log(x): x = get_ov_output(x) x_type = x.get_element_type() From f31e9c58c48819a80dd5ad31f465f0061b63b41b Mon Sep 17 00:00:00 2001 From: Tuan Vo Date: Wed, 23 Apr 2025 03:31:02 +0700 Subject: [PATCH 08/12] fixed linspace implementation --- keras/src/backend/openvino/numpy.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 9ab8db2fcabe..3e534b1bea0d 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -924,7 +924,6 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis start = ov_opset.convert(start, ov_dtype).output(0) stop = ov_opset.convert(stop, ov_dtype).output(0) - # Check if we're dealing with arrays start_shape = start.get_shape() stop_shape = stop.get_shape() is_array_input = len(start_shape) > 0 or len(stop_shape) > 0 @@ -959,21 +958,25 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis ).output(0) if is_array_input: - # For array inputs, we need to reshape the indices - # to properly broadcast with multidimensional steps new_shape = [] new_shape.append(num) for _ in range(len(start_shape)): new_shape.append(1) - indices = ov_opset.reshape(indices, ov_opset.constant(new_shape, Type.i64).output(0)).output(0) + indices = ov_opset.reshape(indices, + ov_opset.constant(new_shape, Type.i64).output(0), + special_zero=False).output(0) scaled_indices = ov_opset.multiply(indices, step).output(0) result = ov_opset.add(start, scaled_indices).output(0) if endpoint and num > 1: last_idx = ov_opset.constant(num - 1, Type.i32).output(0) - # Fix 1: Use the correct function name - result = ov_opset.scatter_elements_update(result, last_idx, stop).output(0) + result = ov_opset.scatter_elements_update( + result, + last_idx, + stop, + axis=0 + ).output(0) if axis != 0: axis_const = ov_opset.constant([axis], Type.i64).output(0) @@ -981,7 +984,9 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis if retstep: return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step) - return OpenVINOKerasTensor(result) + return OpenVINOKerasTensor(result) + + def log(x): x = get_ov_output(x) x_type = x.get_element_type() From 4e4a0b3553d83e268cbf723303a5eb43c8c9a509 Mon Sep 17 00:00:00 2001 From: Tuan Vo Date: Wed, 23 Apr 2025 03:42:00 +0700 Subject: [PATCH 09/12] fixed errors --- keras/src/backend/openvino/numpy.py | 63 ++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 3e534b1bea0d..306c6a8e17aa 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -912,6 +912,17 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis raise TypeError("num must be an integer") if num < 0: raise ValueError("num must be non-negative") + + if num == 0: + if dtype is None: + ov_dtype = OPENVINO_DTYPES[config.floatx()] + else: + ov_dtype = OPENVINO_DTYPES[dtype] + result = ov_opset.constant([], ov_dtype, shape=[0]).output(0) + if retstep: + step = ov_opset.constant(float("nan"), ov_dtype).output(0) + return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step) + return OpenVINOKerasTensor(result) start = get_ov_output(start) stop = get_ov_output(stop) @@ -924,10 +935,19 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis start = ov_opset.convert(start, ov_dtype).output(0) stop = ov_opset.convert(stop, ov_dtype).output(0) - start_shape = start.get_shape() - stop_shape = stop.get_shape() - is_array_input = len(start_shape) > 0 or len(stop_shape) > 0 - + if num == 1: + if endpoint: + result = ov_opset.convert(stop, ov_dtype).output(0) + else: + result = ov_opset.convert(start, ov_dtype).output(0) + if axis != 0: + axis_const = ov_opset.constant([axis], Type.i64).output(0) + result = ov_opset.unsqueeze(result, axis_const).output(0) + if retstep: + step = ov_opset.subtract(stop, start).output(0) + return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step) + return OpenVINOKerasTensor(result) + div = num - 1 if endpoint else num div_const = ov_opset.constant(div, ov_dtype).output(0) delta = ov_opset.subtract(stop, start).output(0) @@ -957,26 +977,29 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis type_str ).output(0) - if is_array_input: - new_shape = [] - new_shape.append(num) - for _ in range(len(start_shape)): - new_shape.append(1) - indices = ov_opset.reshape(indices, - ov_opset.constant(new_shape, Type.i64).output(0), - special_zero=False).output(0) - scaled_indices = ov_opset.multiply(indices, step).output(0) result = ov_opset.add(start, scaled_indices).output(0) if endpoint and num > 1: - last_idx = ov_opset.constant(num - 1, Type.i32).output(0) - result = ov_opset.scatter_elements_update( - result, - last_idx, - stop, - axis=0 + all_but_last = ov_opset.slice( + result, + ov_opset.constant([0], Type.i64).output(0), + ov_opset.constant([num-1], Type.i64).output(0), + ov_opset.constant([1], Type.i64).output(0), + ov_opset.constant([0], Type.i64).output(0) ).output(0) + + stop_shape = stop.get_shape() + result_shape = result.get_shape() + + if len(stop_shape) < len(result_shape): + for _ in range(len(result_shape) - len(stop_shape)): + stop = ov_opset.unsqueeze( + stop, + ov_opset.constant([0], Type.i64).output(0) + ).output(0) + + result = ov_opset.concat([all_but_last, stop], 0).output(0) if axis != 0: axis_const = ov_opset.constant([axis], Type.i64).output(0) @@ -984,7 +1007,7 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis if retstep: return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step) - return OpenVINOKerasTensor(result) + return OpenVINOKerasTensor(result) def log(x): From be3c1145aa4e59659ad12c5bf8ac03a07b420441 Mon Sep 17 00:00:00 2001 From: Tuan Vo Date: Wed, 23 Apr 2025 03:53:01 +0700 Subject: [PATCH 10/12] fixed linspace --- keras/src/backend/openvino/numpy.py | 172 +++++++++++++++------------- 1 file changed, 93 insertions(+), 79 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index 306c6a8e17aa..c714dbf360e7 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -913,101 +913,115 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis if num < 0: raise ValueError("num must be non-negative") - if num == 0: - if dtype is None: - ov_dtype = OPENVINO_DTYPES[config.floatx()] - else: - ov_dtype = OPENVINO_DTYPES[dtype] - result = ov_opset.constant([], ov_dtype, shape=[0]).output(0) - if retstep: - step = ov_opset.constant(float("nan"), ov_dtype).output(0) - return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step) - return OpenVINOKerasTensor(result) - - start = get_ov_output(start) - stop = get_ov_output(stop) - + start_ov = get_ov_output(start) + stop_ov = get_ov_output(stop) + if dtype is None: ov_dtype = OPENVINO_DTYPES[config.floatx()] else: ov_dtype = OPENVINO_DTYPES[dtype] - - start = ov_opset.convert(start, ov_dtype).output(0) - stop = ov_opset.convert(stop, ov_dtype).output(0) - - if num == 1: - if endpoint: - result = ov_opset.convert(stop, ov_dtype).output(0) + + start_ov = ov_opset.convert(start_ov, ov_dtype).output(0) + stop_ov = ov_opset.convert(stop_ov, ov_dtype).output(0) + + start_shape = start_ov.get_shape() + stop_shape = stop_ov.get_shape() + + if num == 0: + if len(start_shape) == 0 and len(stop_shape) == 0: + result = ov_opset.constant(np.array([], dtype=np.dtype(dtype or config.floatx()))).output(0) else: - result = ov_opset.convert(start, ov_dtype).output(0) - if axis != 0: - axis_const = ov_opset.constant([axis], Type.i64).output(0) - result = ov_opset.unsqueeze(result, axis_const).output(0) + out_shape = list(np.broadcast( + np.empty(start_shape, dtype=bool), + np.empty(stop_shape, dtype=bool) + ).shape) + out_shape.insert(axis, 0) + + empty_np = np.empty(out_shape, dtype=np.dtype(dtype or config.floatx())) + empty_np = np.reshape(empty_np, [-1]) + result = ov_opset.constant(empty_np).output(0) + + shape_const = ov_opset.constant(np.array(out_shape, dtype=np.int64)).output(0) + result = ov_opset.reshape(result, shape_const).output(0) + if retstep: - step = ov_opset.subtract(stop, start).output(0) + delta = ov_opset.subtract(stop_ov, start_ov).output(0) + step = delta return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step) return OpenVINOKerasTensor(result) - - div = num - 1 if endpoint else num - div_const = ov_opset.constant(div, ov_dtype).output(0) - delta = ov_opset.subtract(stop, start).output(0) - step = ov_opset.divide(delta, div_const).output(0) - - type_to_str = { - Type.f16: "f16", - Type.f32: "f32", - Type.f64: "f64", - Type.bf16: "bf16", - Type.i8: "i8", - Type.i16: "i16", - Type.i32: "i32", - Type.i64: "i64", - Type.u8: "u8", - Type.u16: "u16", - Type.u32: "u32", - Type.u64: "u64" - } - type_str = type_to_str.get(ov_dtype, "f32") + is_scalar_start = len(start_shape) == 0 + is_scalar_stop = len(stop_shape) == 0 - indices = ov_opset.range( - ov_opset.constant(0, Type.i32).output(0), - ov_opset.constant(num, Type.i32).output(0), - ov_opset.constant(1, Type.i32).output(0), - type_str - ).output(0) + if not (is_scalar_start and is_scalar_stop): + broadcast_shape = list(np.broadcast( + np.empty(start_shape, dtype=bool), + np.empty(stop_shape, dtype=bool) + ).shape) + + if not is_scalar_start and tuple(start_shape) != tuple(broadcast_shape): + shape_const = ov_opset.constant(np.array(broadcast_shape, dtype=np.int64)).output(0) + start_ov = ov_opset.broadcast(start_ov, shape_const).output(0) + + if not is_scalar_stop and tuple(stop_shape) != tuple(broadcast_shape): + shape_const = ov_opset.constant(np.array(broadcast_shape, dtype=np.int64)).output(0) + stop_ov = ov_opset.broadcast(stop_ov, shape_const).output(0) - scaled_indices = ov_opset.multiply(indices, step).output(0) - result = ov_opset.add(start, scaled_indices).output(0) - - if endpoint and num > 1: - all_but_last = ov_opset.slice( - result, - ov_opset.constant([0], Type.i64).output(0), - ov_opset.constant([num-1], Type.i64).output(0), - ov_opset.constant([1], Type.i64).output(0), - ov_opset.constant([0], Type.i64).output(0) - ).output(0) + if num == 1: + if endpoint: + result = stop_ov + else: + result = start_ov + + step = ov_opset.subtract(stop_ov, start_ov).output(0) - stop_shape = stop.get_shape() - result_shape = result.get_shape() + if not (is_scalar_start and is_scalar_stop): + out_shape = list(result.get_shape()) + out_shape.insert(axis, 1) + shape_const = ov_opset.constant(np.array(out_shape, dtype=np.int64)).output(0) + result = ov_opset.reshape(result, shape_const).output(0) + else: + div = num - 1 if endpoint else num + div_const = ov_opset.constant(div, ov_dtype).output(0) + delta = ov_opset.subtract(stop_ov, start_ov).output(0) + step = ov_opset.divide(delta, div_const).output(0) - if len(stop_shape) < len(result_shape): - for _ in range(len(result_shape) - len(stop_shape)): - stop = ov_opset.unsqueeze( - stop, - ov_opset.constant([0], Type.i64).output(0) - ).output(0) + out_shape = list(start_ov.get_shape() if not is_scalar_start else stop_ov.get_shape() if not is_scalar_stop else []) - result = ov_opset.concat([all_but_last, stop], 0).output(0) - - if axis != 0: - axis_const = ov_opset.constant([axis], Type.i64).output(0) - result = ov_opset.unsqueeze(result, axis_const).output(0) - + indices = ov_opset.range( + ov_opset.constant(0, ov_dtype).output(0), + ov_opset.constant(num, ov_dtype).output(0), + ov_opset.constant(1, ov_dtype).output(0) + ).output(0) + + if not (is_scalar_start and is_scalar_stop): + expanded_shape = list(out_shape) + expanded_shape.insert(axis, 1) + shape_const = ov_opset.constant(np.array(expanded_shape, dtype=np.int64)).output(0) + + start_reshaped = ov_opset.reshape(start_ov, shape_const).output(0) + step_reshaped = ov_opset.reshape(step, shape_const).output(0) + + indices_shape = [1] * len(expanded_shape) + indices_shape[axis] = num + indices_shape_const = ov_opset.constant(np.array(indices_shape, dtype=np.int64)).output(0) + indices_reshaped = ov_opset.reshape(indices, indices_shape_const).output(0) + + indices_times_step = ov_opset.multiply(indices_reshaped, step_reshaped).output(0) + result = ov_opset.add(start_reshaped, indices_times_step).output(0) + else: + indices_times_step = ov_opset.multiply(indices, step).output(0) + result = ov_opset.add(start_ov, indices_times_step).output(0) + + if axis != 0: + out_shape = [1] * (axis+1) + out_shape[axis] = num + shape_const = ov_opset.constant(np.array(out_shape, dtype=np.int64)).output(0) + result = ov_opset.reshape(result, shape_const).output(0) + if retstep: return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step) - return OpenVINOKerasTensor(result) + return OpenVINOKerasTensor(result) def log(x): From 8fbaef0ad295f32c5237861db0b59545156fe259 Mon Sep 17 00:00:00 2001 From: Tuan Vo Date: Wed, 23 Apr 2025 04:04:01 +0700 Subject: [PATCH 11/12] fixed errors --- keras/src/backend/openvino/numpy.py | 119 ++++++++-------------------- 1 file changed, 34 insertions(+), 85 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index c714dbf360e7..d63fa7c90bc6 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -919,109 +919,58 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis if dtype is None: ov_dtype = OPENVINO_DTYPES[config.floatx()] else: - ov_dtype = OPENVINO_DTYPES[dtype] + try: + ov_dtype = OPENVINO_DTYPES[dtype] + except KeyError: + ov_dtype = OPENVINO_DTYPES["float32"] start_ov = ov_opset.convert(start_ov, ov_dtype).output(0) stop_ov = ov_opset.convert(stop_ov, ov_dtype).output(0) - start_shape = start_ov.get_shape() - stop_shape = stop_ov.get_shape() - if num == 0: - if len(start_shape) == 0 and len(stop_shape) == 0: - result = ov_opset.constant(np.array([], dtype=np.dtype(dtype or config.floatx()))).output(0) - else: - out_shape = list(np.broadcast( - np.empty(start_shape, dtype=bool), - np.empty(stop_shape, dtype=bool) - ).shape) - out_shape.insert(axis, 0) - - empty_np = np.empty(out_shape, dtype=np.dtype(dtype or config.floatx())) - empty_np = np.reshape(empty_np, [-1]) - result = ov_opset.constant(empty_np).output(0) - - shape_const = ov_opset.constant(np.array(out_shape, dtype=np.int64)).output(0) - result = ov_opset.reshape(result, shape_const).output(0) + empty_array = np.array([], dtype=np.dtype(dtype or config.floatx())) + result = ov_opset.constant(empty_array).output(0) if retstep: delta = ov_opset.subtract(stop_ov, start_ov).output(0) - step = delta - return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step) + return OpenVINOKerasTensor(result), OpenVINOKerasTensor(delta) return OpenVINOKerasTensor(result) - is_scalar_start = len(start_shape) == 0 - is_scalar_stop = len(stop_shape) == 0 - - if not (is_scalar_start and is_scalar_stop): - broadcast_shape = list(np.broadcast( - np.empty(start_shape, dtype=bool), - np.empty(stop_shape, dtype=bool) - ).shape) - - if not is_scalar_start and tuple(start_shape) != tuple(broadcast_shape): - shape_const = ov_opset.constant(np.array(broadcast_shape, dtype=np.int64)).output(0) - start_ov = ov_opset.broadcast(start_ov, shape_const).output(0) - - if not is_scalar_stop and tuple(stop_shape) != tuple(broadcast_shape): - shape_const = ov_opset.constant(np.array(broadcast_shape, dtype=np.int64)).output(0) - stop_ov = ov_opset.broadcast(stop_ov, shape_const).output(0) - if num == 1: if endpoint: - result = stop_ov + result = start_ov if retstep else stop_ov else: result = start_ov - step = ov_opset.subtract(stop_ov, start_ov).output(0) - - if not (is_scalar_start and is_scalar_stop): - out_shape = list(result.get_shape()) - out_shape.insert(axis, 1) - shape_const = ov_opset.constant(np.array(out_shape, dtype=np.int64)).output(0) - result = ov_opset.reshape(result, shape_const).output(0) - else: - div = num - 1 if endpoint else num - div_const = ov_opset.constant(div, ov_dtype).output(0) - delta = ov_opset.subtract(stop_ov, start_ov).output(0) - step = ov_opset.divide(delta, div_const).output(0) - - out_shape = list(start_ov.get_shape() if not is_scalar_start else stop_ov.get_shape() if not is_scalar_stop else []) - - indices = ov_opset.range( - ov_opset.constant(0, ov_dtype).output(0), - ov_opset.constant(num, ov_dtype).output(0), - ov_opset.constant(1, ov_dtype).output(0) - ).output(0) - - if not (is_scalar_start and is_scalar_stop): - expanded_shape = list(out_shape) - expanded_shape.insert(axis, 1) - shape_const = ov_opset.constant(np.array(expanded_shape, dtype=np.int64)).output(0) - - start_reshaped = ov_opset.reshape(start_ov, shape_const).output(0) - step_reshaped = ov_opset.reshape(step, shape_const).output(0) - - indices_shape = [1] * len(expanded_shape) - indices_shape[axis] = num - indices_shape_const = ov_opset.constant(np.array(indices_shape, dtype=np.int64)).output(0) - indices_reshaped = ov_opset.reshape(indices, indices_shape_const).output(0) - - indices_times_step = ov_opset.multiply(indices_reshaped, step_reshaped).output(0) - result = ov_opset.add(start_reshaped, indices_times_step).output(0) - else: - indices_times_step = ov_opset.multiply(indices, step).output(0) - result = ov_opset.add(start_ov, indices_times_step).output(0) - - if axis != 0: - out_shape = [1] * (axis+1) - out_shape[axis] = num - shape_const = ov_opset.constant(np.array(out_shape, dtype=np.int64)).output(0) - result = ov_opset.reshape(result, shape_const).output(0) + if retstep: + delta = ov_opset.subtract(stop_ov, start_ov).output(0) + return OpenVINOKerasTensor(result), OpenVINOKerasTensor(delta) + return OpenVINOKerasTensor(result) + + div = num - 1 if endpoint else num + div_const = ov_opset.constant(div, ov_dtype).output(0) + delta = ov_opset.subtract(stop_ov, start_ov).output(0) + step = ov_opset.divide(delta, div_const).output(0) + + indices = ov_opset.range( + ov_opset.constant(0, element_type=ov_dtype).output(0), + ov_opset.constant(num, element_type=ov_dtype).output(0), + ov_opset.constant(1, element_type=ov_dtype).output(0), + output_type=ov_dtype + ).output(0) + + scaled_indices = ov_opset.multiply(indices, step).output(0) + result = ov_opset.add(start_ov, scaled_indices).output(0) + + if axis != 0: + out_shape = [1] * (axis + 1) + out_shape[axis] = num + shape_const = ov_opset.constant(np.array(out_shape, dtype=np.int64)).output(0) + result = ov_opset.reshape(result, shape_const, special_zero=False).output(0) if retstep: return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step) - return OpenVINOKerasTensor(result) + return OpenVINOKerasTensor(result) def log(x): From 7edfe5ec7a419d5e274890ce011dea15c726ceb0 Mon Sep 17 00:00:00 2001 From: Tuan Vo Date: Wed, 23 Apr 2025 09:58:57 +0700 Subject: [PATCH 12/12] fixed some errors --- keras/src/backend/openvino/numpy.py | 87 ++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/keras/src/backend/openvino/numpy.py b/keras/src/backend/openvino/numpy.py index d63fa7c90bc6..3fed60fe83cd 100644 --- a/keras/src/backend/openvino/numpy.py +++ b/keras/src/backend/openvino/numpy.py @@ -918,18 +918,34 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis if dtype is None: ov_dtype = OPENVINO_DTYPES[config.floatx()] + numpy_dtype = np.dtype(config.floatx()) else: - try: - ov_dtype = OPENVINO_DTYPES[dtype] - except KeyError: + if dtype == "bfloat16": ov_dtype = OPENVINO_DTYPES["float32"] + numpy_dtype = np.dtype("float32") + else: + ov_dtype = OPENVINO_DTYPES[dtype] + numpy_dtype = np.dtype(dtype) start_ov = ov_opset.convert(start_ov, ov_dtype).output(0) stop_ov = ov_opset.convert(stop_ov, ov_dtype).output(0) + start_shape = start_ov.get_shape() + stop_shape = stop_ov.get_shape() + if num == 0: - empty_array = np.array([], dtype=np.dtype(dtype or config.floatx())) - result = ov_opset.constant(empty_array).output(0) + if len(start_shape) == 0 and len(stop_shape) == 0: + empty_array = np.array([], dtype=numpy_dtype) + result = ov_opset.constant(empty_array).output(0) + else: + broadcast_shape = list(np.broadcast( + np.empty(start_shape, dtype=bool), + np.empty(stop_shape, dtype=bool) + ).shape) + broadcast_shape.insert(axis, 0) + + empty_array = np.empty(broadcast_shape, dtype=numpy_dtype) + result = ov_opset.constant(empty_array).output(0) if retstep: delta = ov_opset.subtract(stop_ov, start_ov).output(0) @@ -938,35 +954,64 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis if num == 1: if endpoint: - result = start_ov if retstep else stop_ov + result = start_ov else: result = start_ov + + if len(start_shape) > 0 or len(stop_shape) > 0: + broadcast_shape = list(np.broadcast( + np.empty(start_shape, dtype=bool), + np.empty(stop_shape, dtype=bool) + ).shape) + broadcast_shape.insert(axis, 1) + shape_const = ov_opset.constant(np.array(broadcast_shape, dtype=np.int64)).output(0) + result = ov_opset.reshape(result, shape_const).output(0) + if retstep: delta = ov_opset.subtract(stop_ov, start_ov).output(0) return OpenVINOKerasTensor(result), OpenVINOKerasTensor(delta) return OpenVINOKerasTensor(result) - div = num - 1 if endpoint else num - div_const = ov_opset.constant(div, ov_dtype).output(0) + div = num - 1 if endpoint else num + div_const = ov_opset.constant(float(div)).output(0) delta = ov_opset.subtract(stop_ov, start_ov).output(0) step = ov_opset.divide(delta, div_const).output(0) - indices = ov_opset.range( - ov_opset.constant(0, element_type=ov_dtype).output(0), - ov_opset.constant(num, element_type=ov_dtype).output(0), - ov_opset.constant(1, element_type=ov_dtype).output(0), - output_type=ov_dtype - ).output(0) + indices_np = np.arange(num, dtype=numpy_dtype) + indices = ov_opset.constant(indices_np).output(0) - scaled_indices = ov_opset.multiply(indices, step).output(0) - result = ov_opset.add(start_ov, scaled_indices).output(0) + is_scalar_input = len(start_shape) == 0 and len(stop_shape) == 0 - if axis != 0: - out_shape = [1] * (axis + 1) - out_shape[axis] = num - shape_const = ov_opset.constant(np.array(out_shape, dtype=np.int64)).output(0) - result = ov_opset.reshape(result, shape_const, special_zero=False).output(0) + if is_scalar_input: + scaled_indices = ov_opset.multiply(indices, step).output(0) + result = ov_opset.add(start_ov, scaled_indices).output(0) + + if axis != 0: + out_shape = [1] * (axis + 1) + out_shape[axis] = num + shape_const = ov_opset.constant(np.array(out_shape, dtype=np.int64)).output(0) + result = ov_opset.reshape(result, shape_const).output(0) + else: + broadcast_shape = list(np.broadcast( + np.empty(start_shape, dtype=bool), + np.empty(stop_shape, dtype=bool) + ).shape) + + expanded_shape = broadcast_shape.copy() + expanded_shape.insert(axis, 1) + + shape_const = ov_opset.constant(np.array(expanded_shape, dtype=np.int64)).output(0) + start_reshaped = ov_opset.reshape(start_ov, shape_const).output(0) + step_reshaped = ov_opset.reshape(step, shape_const).output(0) + + indices_shape = [1] * len(expanded_shape) + indices_shape[axis] = num + indices_shape_const = ov_opset.constant(np.array(indices_shape, dtype=np.int64)).output(0) + indices_reshaped = ov_opset.reshape(indices, indices_shape_const).output(0) + + scaled_indices = ov_opset.multiply(indices_reshaped, step_reshaped).output(0) + result = ov_opset.add(start_reshaped, scaled_indices).output(0) if retstep: return OpenVINOKerasTensor(result), OpenVINOKerasTensor(step)