Skip to content

Commit

Permalink
[Spec][Opset15] Add ScatterNdUpdate-15 specification (openvinotoolkit…
Browse files Browse the repository at this point in the history
…#23443)

### Details:
 - *Specification for ScatterNdUpdate-15*
 - *Add reduction attribute*
 - *Improve compatibility with TF TensorScattterUpdate op

### Tickets:
 - *11089*
  • Loading branch information
mmikolajcz authored Apr 18, 2024
1 parent 176e63c commit d4c7063
Showing 1 changed file with 230 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
.. {#openvino_docs_ops_movement_ScatterNDUpdate_15}
ScatterNDUpdate
===============


.. meta::
:description: Learn about ScatterNDUpdate-15 - a data movement operation, which can be
performed on three required input tensors.

**Versioned name**: *ScatterNDUpdate-15*

**Category**: *Data movement*

**Short description**: Creates a copy of the ``data`` input tensor with updates for elements specified by ``indices`` by elements from ``updates`` according to *reduction* attribute.

**Detailed description**: The operation produces a copy of ``data`` tensor and updates its value using logic from ``reduction`` attribute, using values specified
by ``updates`` at specific index positions specified by ``indices``. The output shape is the same as the shape of ``data``.
Input ``indices`` can contain duplicated index values, however, in case when *reduction* is set to ``none``, only last update for given duplicated index is used.

The last dimension of ``indices`` corresponds to indices into elements if ``indices.shape[-1]`` = ``data.shape.rank`` or slices
if ``indices.shape[-1]`` < ``data.shape.rank``.
Input ``updates`` is a tensor with shape ``indices.shape[:-1] + data.shape[indices.shape[-1]:]``.

The operation to perform between the corresponding elements is specified by reduction attribute, by default the elements of data tensor are simply overwritten by the values from updates input.

Operator ScatterNDUpdate-15 is an equivalent to following NumPy snippet:

.. code-block:: py
def scatter_nd_update_15(data, indices, updates, reduction=None):
func = lambda x, y: y
if reduction == "sum":
func = lambda x, y: x + y
elif reduction == "sub":
func = lambda x, y: x - y
elif reduction == "prod":
func = lambda x, y: x * y
elif reduction == "max":
func = max
elif reduction == "min":
func = min
out = np.copy(data)
for ndidx in np.ndindex(indices.shape[:-1]):
out[indices[ndidx]] = func(out[indices[ndidx]], updates[ndidx])
return out
Example 1 that shows simple case of update with *reduction* set to ``none``.:

.. code-block:: cpp
data = [1, 2, 3, 4, 5, 6, 7, 8]
indices = [[4], [3], [1], [7], [-2], [-4]]
updates = [9, 10, 11, 12, 13, 14]
output = [1, 11, 3, 10, 14, 6, 13, 12]
Example that shows update of two slices of ``4x4`` shape in ``data``, with *reduction* set to ``none``:

.. code-block:: cpp
data = [[[1, 2, 3, 4], [5, 6, 7, 8], [8, 7, 6, 5], [4, 3, 2, 1]],
[[1, 2, 3, 4], [5, 6, 7, 8], [8, 7, 6, 5], [4, 3, 2, 1]],
[[8, 7, 6, 5], [4, 3, 2, 1], [1, 2, 3, 4], [5, 6, 7, 8]],
[[8, 7, 6, 5], [4, 3, 2, 1], [1, 2, 3, 4], [5, 6, 7, 8]]]
indices = [[0], [2]]
updates = [[[5, 5, 5, 5], [6, 6, 6, 6], [7, 7, 7, 7], [8, 8, 8, 8]],
[[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]]]
output = [[[5, 5, 5, 5], [6, 6, 6, 6], [7, 7, 7, 7], [8, 8, 8, 8]],
[[1, 2, 3, 4], [5, 6, 7, 8], [8, 7, 6, 5], [4, 3, 2, 1]],
[[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]],
[[8, 7, 6, 5], [4, 3, 2, 1], [1, 2, 3, 4], [5, 6, 7, 8]]]
**Attributes**:

* *reduction*

* **Description**: The type of operation to perform on the inputs.
* **Range of values**: one of ``none``, ``sum``, ``sub``, ``prod``, ``min``, ``max``
* **Type**: `string`
* **Default value**: ``none``
* **Required**: *no*

**Inputs**:

* **1**: ``data`` tensor of arbitrary rank ``r`` >= 1 and of type *T*. **Required.**

* **2**: ``indices`` tensor with indices of arbitrary rank ``q`` >= 1 and of type *T_IND*. All index values ``i_j`` in index entry ``(i_0, i_1, ...,i_k)`` (where ``k = indices.shape[-1]``) must be within bounds ``[-s_j, s_j - 1]`` where ``s_j = data.shape[j]``. ``k`` must be at most ``r``. If multiple indices point to the same output location then values will be updated in order of their occurrence. Negative value of index means reverse indexing and will be normalized to value ``len(data.shape[j] + index)``. If an index points to non-existing element then exception is raised. **Required.**

* **3**: ``updates`` tensor of rank ``r - indices.shape[-1] + q - 1`` of type *T*. If expected ``updates`` rank is 0D it can be a tensor with single element. **Required.**

**Outputs**:

* **1**: tensor with shape equal to ``data`` tensor of the type *T*.

**Types**

* *T*: any numeric type. For boolean type, reduction sum, sub, prod behaves like logical OR, XOR, AND accordingly.

* *T_IND*: ``int32`` or ``int64``

**Example**

*Example 1*

.. code-block:: xml
<layer ... reduction="none" type="ScatterNdUpdate">
<input>
<port id="0" precision="FP32"> <!-- data -->
<dim>4</dim> <!-- values: [1, 2, 3, 4] -->
</port>
<port id="1" precision="I32"> <!-- indices -->
<dim>5</dim> <!-- values: [0, 2, -3, -3, 0] -->
</port>
<port id="2" precision="FP32"> <!-- updates -->
<dim>5</dim> <!-- values: [10, 20, 30, 40, 50] -->
</port>
</input>
<output>
<port id="3" precision="FP32">
<dim>4</dim> <!-- values: [50, 40, 20, 4] -->
</port>
</output>
</layer>
*Example 2*

.. code-block:: xml
<layer ... reduction="sum" type="ScatterNdUpdate">
<input>
<port id="0" precision="FP16"> <!-- data -->
<dim>4</dim> <!-- values: [1, 2, 3, 4] -->
</port>
<port id="1" precision="I32"> <!-- indices -->
<dim>5</dim> <!-- values: [0, 2, -3, -3, 0] -->
</port>
<port id="2" precision="FP16"> <!-- updates -->
<dim>5</dim> <!-- values: [10, 20, 30, 40, 50] -->
</port>
</input>
<output>
<port id="3" precision="FP16">
<dim>4</dim> <!-- values: [61, 72, 23, 4] -->
</port>
</output>
</layer>
*Example 3*

.. code-block:: xml
<layer ... reduction="sub" type="ScatterNdUpdate">
<input>
<port id="0" precision="I32"> <!-- data -->
<dim>4</dim> <!-- values: [1, 2, 3, 4] -->
</port>
<port id="1" precision="I32"> <!-- indices -->
<dim>5</dim> <!-- values: [0, 2, -3, -3, 0] -->
</port>
<port id="2" precision="I32"> <!-- updates -->
<dim>5</dim> <!-- values: [10, 20, 30, 40, 50] -->
</port>
</input>
<output>
<port id="3" precision="I32">
<dim>4</dim> <!-- values: [-59, -68, -17, 4] -->
</port>
</output>
</layer>
*Example 4*

.. code-block:: xml
<layer ... reduction="prod" type="ScatterNdUpdate">
<input>
<port id="0" precision="FP32"> <!-- data -->
<dim>4</dim> <!-- values: [1, 2, 3, 4] -->
</port>
<port id="1" precision="I32"> <!-- indices -->
<dim>5</dim> <!-- values: [0, 2, -3, -3, 0] -->
</port>
<port id="2" precision="FP32"> <!-- updates -->
<dim>5</dim> <!-- values: [10, 20, 30, 40, 50] -->
</port>
</input>
<output>
<port id="3" precision="FP32">
<dim>4</dim> <!-- values: [500, 3600, 40, 4] -->
</port>
</output>
</layer>
*Example 5*

.. code-block:: xml
:force:
<layer ... reduction="none" type="ScatterNDUpdate">
<input>
<port id="0">
<dim>1000</dim>
<dim>256</dim>
<dim>10</dim>
<dim>15</dim>
</port>
<port id="1">
<dim>25</dim>
<dim>125</dim>
<dim>3</dim>
</port>
<port id="2">
<dim>25</dim>
<dim>125</dim>
<dim>15</dim>
</port>
</input>
<output>
<port id="3">
<dim>1000</dim>
<dim>256</dim>
<dim>10</dim>
<dim>15</dim>
</port>
</output>
</layer>

0 comments on commit d4c7063

Please sign in to comment.