未验证 提交 db1a9f23 编写于 作者: S Shubham Bhokare 提交者: GitHub

Update Scatter style ops: Add reduction attribute (#3484)

* Initial doc changes
Signed-off-by: NShubham Bhokare <shubhambhokare@gmail.com>

* Add expect function logics
Signed-off-by: NShubham Bhokare <shubhambhokare@gmail.com>

* Update multiply -> mul
Signed-off-by: NShubham Bhokare <shubhambhokare@gmail.com>

* Added newline
Signed-off-by: NShubham Bhokare <shubhambhokare@gmail.com>

* Fixed CI issues, added changelogs
Signed-off-by: NShubham Bhokare <shubhambhokare@gmail.com>

* Add version converters
Signed-off-by: NShubham Bhokare <shubhambhokare@gmail.com>

* Fix trailing spaces
Signed-off-by: NShubham Bhokare <shubhambhokare@gmail.com>

* Add type annotation
Signed-off-by: NShubham Bhokare <shubhambhokare@gmail.com>

* Use compatible adapters
Signed-off-by: NShubham Bhokare <shubhambhokare@gmail.com>

* Remove git messages
Signed-off-by: NShubham Bhokare <shubhambhokare@gmail.com>

* Remove git messages
Signed-off-by: NShubham Bhokare <shubhambhokare@gmail.com>
Co-authored-by: NShubham Bhokare <sbhokare@OrtTrainingDev3.af05slrtruoetgaxwwjv5nsq5e.px.internal.cloudapp.net>
上级 f61e2a4a
......@@ -20161,6 +20161,226 @@ This version of the operator has been available since version 16 of the default
<dd>Constrain types to int tensors.</dd>
</dl>
### <a name="ScatterElements-16"></a>**ScatterElements-16**</a>
ScatterElements takes three inputs `data`, `updates`, and `indices` of the same
rank r >= 1 and an optional attribute axis that identifies an axis of `data`
(by default, the outer-most axis, that is axis 0). The output of the operation
is produced by creating a copy of the input `data`, and then updating its value
to values specified by `updates` at specific index positions specified by
`indices`. Its output shape is the same as the shape of `data`.
For each entry in `updates`, the target index in `data` is obtained by combining
the corresponding entry in `indices` with the index of the entry itself: the
index-value for dimension = axis is obtained from the value of the corresponding
entry in `indices` and the index-value for dimension != axis is obtained from the
index of the entry itself.
`reduction` allows specification of an optional reduction operation, which is applied to all values in `updates`
tensor into `output` at the specified `indices`.
In cases where `reduction` is set to "none", indices should not have duplicate entries: that is, if idx1 != idx2,
then indices[idx1] != indices[idx2]. For instance, in a 2-D tensor case, the update
corresponding to the [i][j] entry is performed as below:
```
output[indices[i][j]][j] = updates[i][j] if axis = 0,
output[i][indices[i][j]] = updates[i][j] if axis = 1,
```
When `reduction` is set to "add", the update corresponding to the [i][j] entry is performed as below:
```
output[indices[i][j]][j] += updates[i][j] if axis = 0,
output[i][indices[i][j]] += updates[i][j] if axis = 1,
```
When `reduction` is set to "mul", the update corresponding to the [i][j] entry is performed as below:
```
output[indices[i][j]][j] *= updates[i][j] if axis = 0,
output[i][indices[i][j]] *= updates[i][j] if axis = 1,
```
This operator is the inverse of GatherElements. It is similar to Torch's Scatter operation.
Example 1:
```
data = [
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
]
indices = [
[1, 0, 2],
[0, 2, 1],
]
updates = [
[1.0, 1.1, 1.2],
[2.0, 2.1, 2.2],
]
output = [
[2.0, 1.1, 0.0]
[1.0, 0.0, 2.2]
[0.0, 2.1, 1.2]
]
```
Example 2:
```
data = [[1.0, 2.0, 3.0, 4.0, 5.0]]
indices = [[1, 3]]
updates = [[1.1, 2.1]]
axis = 1
output = [[1.0, 1.1, 3.0, 2.1, 5.0]]
```
#### Version
This version of the operator has been available since version 16 of the default ONNX operator set.
#### Attributes
<dl>
<dt><tt>axis</tt> : int (default is 0)</dt>
<dd>Which axis to scatter on. Negative value means counting dimensions from the back. Accepted range is [-r, r-1] where r = rank(data).</dd>
<dt><tt>reduction</tt> : string (default is none)</dt>
<dd>Type of reduction to apply: none (default), add, mul. 'none': no reduction applied. 'add': reduction using the addition operation. 'mul': reduction using the multiplication operation.</dd>
</dl>
#### Inputs
<dl>
<dt><tt>data</tt> (differentiable) : T</dt>
<dd>Tensor of rank r >= 1.</dd>
<dt><tt>indices</tt> (non-differentiable) : Tind</dt>
<dd>Tensor of int32/int64 indices, of r >= 1 (same rank as input). All index values are expected to be within bounds [-s, s-1] along axis of size s. It is an error if any of the index values are out of bounds.</dd>
<dt><tt>updates</tt> (differentiable) : T</dt>
<dd>Tensor of rank r >=1 (same rank and shape as indices)</dd>
</dl>
#### Outputs
<dl>
<dt><tt>output</tt> (differentiable) : T</dt>
<dd>Tensor of rank r >= 1 (same rank as input).</dd>
</dl>
#### Type Constraints
<dl>
<dt><tt>T</tt> : tensor(uint8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(int8), tensor(int16), tensor(int32), tensor(int64), tensor(bfloat16), tensor(float16), tensor(float), tensor(double), tensor(string), tensor(bool), tensor(complex64), tensor(complex128)</dt>
<dd>Input and output types can be of any tensor type.</dd>
<dt><tt>Tind</tt> : tensor(int32), tensor(int64)</dt>
<dd>Constrain indices to integer types</dd>
</dl>
### <a name="ScatterND-16"></a>**ScatterND-16**</a>
ScatterND takes three inputs `data` tensor of rank r >= 1, `indices` tensor of rank q >= 1,
and `updates` tensor of rank q + r - indices.shape[-1] - 1. The output of the operation
is produced by creating a copy of the input `data`, and then updating its value to values
specified by `updates` at specific index positions specified by `indices`. Its output shape
is the same as the shape of `data`. Note that `indices` should not have duplicate entries.
That is, two or more `updates` for the same index-location is not supported.
`indices` is an integer tensor. Let k denote indices.shape[-1], the last dimension in the shape of `indices`.
`indices` is treated as a (q-1)-dimensional tensor of k-tuples, where each k-tuple is a partial-index into `data`.
Hence, k can be a value at most the rank of `data`. When k equals rank(data), each update entry specifies an
update to a single element of the tensor. When k is less than rank(data) each update entry specifies an
update to a slice of the tensor.
`updates` is treated as a (q-1)-dimensional tensor of replacement-slice-values. Thus, the
first (q-1) dimensions of updates.shape must match the first (q-1) dimensions of indices.shape.
The remaining dimensions of `updates` correspond to the dimensions of the
replacement-slice-values. Each replacement-slice-value is a (r-k) dimensional tensor,
corresponding to the trailing (r-k) dimensions of `data`. Thus, the shape of `updates`
must equal indices.shape[0:q-1] ++ data.shape[k:r-1], where ++ denotes the concatenation
of shapes.
The `output` is calculated via the following equation:
output = np.copy(data)
update_indices = indices.shape[:-1]
for idx in np.ndindex(update_indices):
output[indices[idx]] = updates[idx]
The order of iteration in the above loop is not specified.
In particular, indices should not have duplicate entries: that is, if idx1 != idx2, then indices[idx1] != indices[idx2].
This ensures that the output value does not depend on the iteration order.
`reduction` allows specification of an optional reduction operation, which is applied to all values in `updates`
tensor into `output` at the specified `indices`.
In cases where `reduction` is set to "none", indices should not have duplicate entries: that is, if idx1 != idx2,
then indices[idx1] != indices[idx2]. This ensures that the output value does not depend on the iteration order.
When `reduction` is set to "add", `output` is calculated as follows:
output = np.copy(data)
update_indices = indices.shape[:-1]
for idx in np.ndindex(update_indices):
output[indices[idx]] += updates[idx]
When `reduction` is set to "mul", `output` is calculated as follows:
output = np.copy(data)
update_indices = indices.shape[:-1]
for idx in np.ndindex(update_indices):
output[indices[idx]] *= updates[idx]
This operator is the inverse of GatherND.
Example 1:
```
data = [1, 2, 3, 4, 5, 6, 7, 8]
indices = [[4], [3], [1], [7]]
updates = [9, 10, 11, 12]
output = [1, 11, 3, 10, 9, 6, 7, 12]
```
Example 2:
```
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]]]
```
#### Version
This version of the operator has been available since version 16 of the default ONNX operator set.
#### Attributes
<dl>
<dt><tt>reduction</tt> : string (default is none)</dt>
<dd>Type of reduction to apply: none (default), add, mul. 'none': no reduction applied. 'add': reduction using the addition operation. 'mul': reduction using the multiplication operation.</dd>
</dl>
#### Inputs
<dl>
<dt><tt>data</tt> (differentiable) : T</dt>
<dd>Tensor of rank r >= 1.</dd>
<dt><tt>indices</tt> (non-differentiable) : tensor(int64)</dt>
<dd>Tensor of rank q >= 1.</dd>
<dt><tt>updates</tt> (differentiable) : T</dt>
<dd>Tensor of rank q + r - indices_shape[-1] - 1.</dd>
</dl>
#### Outputs
<dl>
<dt><tt>output</tt> (differentiable) : T</dt>
<dd>Tensor of rank r >= 1.</dd>
</dl>
#### Type Constraints
<dl>
<dt><tt>T</tt> : tensor(uint8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(int8), tensor(int16), tensor(int32), tensor(int64), tensor(bfloat16), tensor(float16), tensor(float), tensor(double), tensor(string), tensor(bool), tensor(complex64), tensor(complex128)</dt>
<dd>Constrain input and output types to any tensor type.</dd>
</dl>
# ai.onnx.preview.training
## Version 1 of the 'ai.onnx.preview.training' operator set
### <a name="ai.onnx.preview.training.Adagrad-1"></a>**ai.onnx.preview.training.Adagrad-1**</a>
......
......@@ -127,8 +127,8 @@ For an operator input/output's differentiability, it can be differentiable,
|<a href="#Round">Round</a>|<a href="Changelog.md#Round-11">11</a>|
|<a href="#Scan">Scan</a>|<a href="Changelog.md#Scan-11">11</a>, <a href="Changelog.md#Scan-9">9</a>, <a href="Changelog.md#Scan-8">8</a>|
|<a href="#Scatter">Scatter</a> (deprecated)|<a href="Changelog.md#Scatter-11">11</a>, <a href="Changelog.md#Scatter-9">9</a>|
|<a href="#ScatterElements">ScatterElements</a>|<a href="Changelog.md#ScatterElements-13">13</a>, <a href="Changelog.md#ScatterElements-11">11</a>|
|<a href="#ScatterND">ScatterND</a>|<a href="Changelog.md#ScatterND-13">13</a>, <a href="Changelog.md#ScatterND-11">11</a>|
|<a href="#ScatterElements">ScatterElements</a>|<a href="Changelog.md#ScatterElements-16">16</a>, <a href="Changelog.md#ScatterElements-13">13</a>, <a href="Changelog.md#ScatterElements-11">11</a>|
|<a href="#ScatterND">ScatterND</a>|<a href="Changelog.md#ScatterND-16">16</a>, <a href="Changelog.md#ScatterND-13">13</a>, <a href="Changelog.md#ScatterND-11">11</a>|
|<a href="#Selu">Selu</a>|<a href="Changelog.md#Selu-6">6</a>, <a href="Changelog.md#Selu-1">1</a>|
|<a href="#SequenceAt">SequenceAt</a>|<a href="Changelog.md#SequenceAt-11">11</a>|
|<a href="#SequenceConstruct">SequenceConstruct</a>|<a href="Changelog.md#SequenceConstruct-11">11</a>|
......@@ -18477,12 +18477,25 @@ expect(node, inputs=[data, indices, updates], outputs=[y],
entry in `indices` and the index-value for dimension != axis is obtained from the
index of the entry itself.
For instance, in a 2-D tensor case, the update corresponding to the [i][j] entry
is performed as below:
`reduction` allows specification of an optional reduction operation, which is applied to all values in `updates`
tensor into `output` at the specified `indices`.
In cases where `reduction` is set to "none", indices should not have duplicate entries: that is, if idx1 != idx2,
then indices[idx1] != indices[idx2]. For instance, in a 2-D tensor case, the update
corresponding to the [i][j] entry is performed as below:
```
output[indices[i][j]][j] = updates[i][j] if axis = 0,
output[i][indices[i][j]] = updates[i][j] if axis = 1,
```
When `reduction` is set to "add", the update corresponding to the [i][j] entry is performed as below:
```
output[indices[i][j]][j] += updates[i][j] if axis = 0,
output[i][indices[i][j]] += updates[i][j] if axis = 1,
```
When `reduction` is set to "mul", the update corresponding to the [i][j] entry is performed as below:
```
output[indices[i][j]][j] *= updates[i][j] if axis = 0,
output[i][indices[i][j]] *= updates[i][j] if axis = 1,
```
This operator is the inverse of GatherElements. It is similar to Torch's Scatter operation.
......@@ -18518,15 +18531,17 @@ expect(node, inputs=[data, indices, updates], outputs=[y],
#### Version
This version of the operator has been available since version 13 of the default ONNX operator set.
This version of the operator has been available since version 16 of the default ONNX operator set.
Other versions of this operator: <a href="Changelog.md#ScatterElements-11">11</a>
Other versions of this operator: <a href="Changelog.md#ScatterElements-11">11</a>, <a href="Changelog.md#ScatterElements-13">13</a>
#### Attributes
<dl>
<dt><tt>axis</tt> : int (default is 0)</dt>
<dd>Which axis to scatter on. Negative value means counting dimensions from the back. Accepted range is [-r, r-1] where r = rank(data).</dd>
<dt><tt>reduction</tt> : string (default is none)</dt>
<dd>Type of reduction to apply: none (default), add, mul. 'none': no reduction applied. 'add': reduction using the addition operation. 'mul': reduction using the multiplication operation.</dd>
</dl>
#### Inputs
......@@ -18585,6 +18600,33 @@ expect(node, inputs=[data, indices, updates], outputs=[y],
</details>
<details>
<summary>scatter_elements_with_duplicate_indices</summary>
```python
axis = 1
node = onnx.helper.make_node(
'ScatterElements',
inputs=['data', 'indices', 'updates'],
outputs=['y'],
axis=axis,
reduction='add',
)
data = np.array([[1.0, 2.0, 3.0, 4.0, 5.0]], dtype=np.float32)
indices = np.array([[1, 1]], dtype=np.int64)
updates = np.array([[1.1, 2.1]], dtype=np.float32)
y = scatter_elements(data, indices, updates, axis, reduction='add')
# print(y) produces
# [[1.0, 5.2, 3.0, 4.0, 5.0]]
expect(node, inputs=[data, indices, updates], outputs=[y],
name='test_scatter_elements_with_duplicate_indices')
```
</details>
<details>
<summary>scatter_elements_with_negative_indices</summary>
......@@ -18671,6 +18713,24 @@ expect(node, inputs=[data, indices, updates], outputs=[y],
In particular, indices should not have duplicate entries: that is, if idx1 != idx2, then indices[idx1] != indices[idx2].
This ensures that the output value does not depend on the iteration order.
`reduction` allows specification of an optional reduction operation, which is applied to all values in `updates`
tensor into `output` at the specified `indices`.
In cases where `reduction` is set to "none", indices should not have duplicate entries: that is, if idx1 != idx2,
then indices[idx1] != indices[idx2]. This ensures that the output value does not depend on the iteration order.
When `reduction` is set to "add", `output` is calculated as follows:
output = np.copy(data)
update_indices = indices.shape[:-1]
for idx in np.ndindex(update_indices):
output[indices[idx]] += updates[idx]
When `reduction` is set to "mul", `output` is calculated as follows:
output = np.copy(data)
update_indices = indices.shape[:-1]
for idx in np.ndindex(update_indices):
output[indices[idx]] *= updates[idx]
This operator is the inverse of GatherND.
Example 1:
......@@ -18698,9 +18758,16 @@ expect(node, inputs=[data, indices, updates], outputs=[y],
#### Version
This version of the operator has been available since version 13 of the default ONNX operator set.
This version of the operator has been available since version 16 of the default ONNX operator set.
Other versions of this operator: <a href="Changelog.md#ScatterND-11">11</a>, <a href="Changelog.md#ScatterND-13">13</a>
Other versions of this operator: <a href="Changelog.md#ScatterND-11">11</a>
#### Attributes
<dl>
<dt><tt>reduction</tt> : string (default is none)</dt>
<dd>Type of reduction to apply: none (default), add, mul. 'none': no reduction applied. 'add': reduction using the addition operation. 'mul': reduction using the multiplication operation.</dd>
</dl>
#### Inputs
......@@ -18761,6 +18828,70 @@ expect(node, inputs=[data, indices, updates], outputs=[output],
</details>
<details>
<summary>scatternd_add</summary>
```python
node = onnx.helper.make_node(
'ScatterND',
inputs=['data', 'indices', 'updates'],
outputs=['y'],
reduction='add',
)
data = np.array(
[[[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]]], dtype=np.float32)
indices = np.array([[0], [0]], dtype=np.int64)
updates = np.array(
[[[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]]], dtype=np.float32)
# Expecting output as np.array(
# [[[7, 8, 9, 10], [13, 14, 15, 16], [18, 17, 16, 15], [16, 15, 14, 13]],
# [[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]]], dtype=np.float32)
output = scatter_nd_impl(data, indices, updates, reduction='add')
expect(node, inputs=[data, indices, updates], outputs=[output],
name='test_scatternd_add')
```
</details>
<details>
<summary>scatternd_multiply</summary>
```python
node = onnx.helper.make_node(
'ScatterND',
inputs=['data', 'indices', 'updates'],
outputs=['y'],
reduction='mul',
)
data = np.array(
[[[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]]], dtype=np.float32)
indices = np.array([[0], [0]], dtype=np.int64)
updates = np.array(
[[[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]]], dtype=np.float32)
# Expecting output as np.array(
# [[[5, 10, 15, 20], [60, 72, 84, 96], [168, 147, 126, 105], [128, 96, 64, 32]],
# [[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]]], dtype=np.float32)
output = scatter_nd_impl(data, indices, updates, reduction='mul')
expect(node, inputs=[data, indices, updates], outputs=[output],
name='test_scatternd_multiply')
```
</details>
### <a name="Selu"></a><a name="selu">**Selu**</a>
Selu takes one input data (Tensor<T>) and produces one output data
......
......@@ -11538,7 +11538,7 @@ expect(node, inputs=[data, indices, updates], outputs=[y],
### ScatterElements
There are 3 test cases, listed as following:
There are 4 test cases, listed as following:
<details>
<summary>scatter_elements_with_axis</summary>
......@@ -11562,6 +11562,31 @@ expect(node, inputs=[data, indices, updates], outputs=[y],
name='test_scatter_elements_with_axis')
```
</details>
<details>
<summary>scatter_elements_with_duplicate_indices</summary>
```python
axis = 1
node = onnx.helper.make_node(
'ScatterElements',
inputs=['data', 'indices', 'updates'],
outputs=['y'],
axis=axis,
reduction='add',
)
data = np.array([[1.0, 2.0, 3.0, 4.0, 5.0]], dtype=np.float32)
indices = np.array([[1, 1]], dtype=np.int64)
updates = np.array([[1.1, 2.1]], dtype=np.float32)
y = scatter_elements(data, indices, updates, axis, reduction='add')
# print(y) produces
# [[1.0, 5.2, 3.0, 4.0, 5.0]]
expect(node, inputs=[data, indices, updates], outputs=[y],
name='test_scatter_elements_with_duplicate_indices')
```
</details>
<details>
<summary>scatter_elements_with_negative_indices</summary>
......@@ -11614,7 +11639,7 @@ expect(node, inputs=[data, indices, updates], outputs=[y],
### ScatterND
There are 1 test cases, listed as following:
There are 3 test cases, listed as following:
<details>
<summary>scatternd</summary>
......@@ -11643,6 +11668,66 @@ expect(node, inputs=[data, indices, updates], outputs=[output],
name='test_scatternd')
```
</details>
<details>
<summary>scatternd_add</summary>
```python
node = onnx.helper.make_node(
'ScatterND',
inputs=['data', 'indices', 'updates'],
outputs=['y'],
reduction='add',
)
data = np.array(
[[[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]]], dtype=np.float32)
indices = np.array([[0], [0]], dtype=np.int64)
updates = np.array(
[[[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]]], dtype=np.float32)
# Expecting output as np.array(
# [[[7, 8, 9, 10], [13, 14, 15, 16], [18, 17, 16, 15], [16, 15, 14, 13]],
# [[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]]], dtype=np.float32)
output = scatter_nd_impl(data, indices, updates, reduction='add')
expect(node, inputs=[data, indices, updates], outputs=[output],
name='test_scatternd_add')
```
</details>
<details>
<summary>scatternd_multiply</summary>
```python
node = onnx.helper.make_node(
'ScatterND',
inputs=['data', 'indices', 'updates'],
outputs=['y'],
reduction='mul',
)
data = np.array(
[[[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]]], dtype=np.float32)
indices = np.array([[0], [0]], dtype=np.int64)
updates = np.array(
[[[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]]], dtype=np.float32)
# Expecting output as np.array(
# [[[5, 10, 15, 20], [60, 72, 84, 96], [168, 147, 126, 105], [128, 96, 64, 32]],
# [[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]]], dtype=np.float32)
output = scatter_nd_impl(data, indices, updates, reduction='mul')
expect(node, inputs=[data, indices, updates], outputs=[output],
name='test_scatternd_multiply')
```
</details>
......
......@@ -13,7 +13,7 @@ from . import expect
# The below ScatterElements' numpy implementation is from https://stackoverflow.com/a/46204790/11767360
def scatter_elements(data, indices, updates, axis=0): # type: ignore
def scatter_elements(data, indices, updates, axis=0, reduction='none'): # type: ignore
if axis < 0:
axis = data.ndim + axis
......@@ -30,6 +30,12 @@ def scatter_elements(data, indices, updates, axis=0): # type: ignore
unpacked = unpacked, packed[i]
return unpacked
def make_indices_for_duplicate(idx): # type: ignore
final_idx = list()
for i in range(len(idx[0])):
final_idx.append(tuple(idx_element[i] for idx_element in idx))
return list(final_idx)
# We use indices and axis parameters to create idx
# idx is in a form that can be used as a NumPy advanced indices for scattering of updates param. in data
idx = [[unpack(np.indices(idx_xsection_shape).reshape(indices.ndim - 1, -1)),
......@@ -43,7 +49,15 @@ def scatter_elements(data, indices, updates, axis=0): # type: ignore
updates_idx.insert(axis, np.repeat(np.arange(indices.shape[axis]), np.prod(idx_xsection_shape)))
scattered = np.copy(data)
scattered[tuple(idx)] = updates[tuple(updates_idx)]
if reduction == 'none':
scattered[tuple(idx)] = updates[tuple(updates_idx)]
else:
idx, updates_idx = make_indices_for_duplicate(idx), make_indices_for_duplicate(updates_idx)
for iter, idx_set in enumerate(idx):
if reduction == 'add':
scattered[idx_set] += updates[updates_idx[iter]]
elif reduction == 'mul':
scattered[idx_set] *= updates[updates_idx[iter]]
return scattered
......@@ -108,3 +122,24 @@ class ScatterElements(Base):
expect(node, inputs=[data, indices, updates], outputs=[y],
name='test_scatter_elements_with_negative_indices')
@staticmethod
def export_scatter_elements_with_duplicate_indices(): # type: () -> None
axis = 1
node = onnx.helper.make_node(
'ScatterElements',
inputs=['data', 'indices', 'updates'],
outputs=['y'],
axis=axis,
reduction='add',
)
data = np.array([[1.0, 2.0, 3.0, 4.0, 5.0]], dtype=np.float32)
indices = np.array([[1, 1]], dtype=np.int64)
updates = np.array([[1.1, 2.1]], dtype=np.float32)
y = scatter_elements(data, indices, updates, axis, reduction='add')
# print(y) produces
# [[1.0, 5.2, 3.0, 4.0, 5.0]]
expect(node, inputs=[data, indices, updates], outputs=[y],
name='test_scatter_elements_with_duplicate_indices')
......@@ -12,8 +12,7 @@ from ..base import Base
from . import expect
def scatter_nd_impl(data, indices, updates):
# type: (np.ndarray, np.ndarray, np.ndarray) -> np.ndarray
def scatter_nd_impl(data, indices, updates, reduction='none'): # type: ignore
# Check tensor shapes
assert indices.shape[-1] <= len(data.shape)
......@@ -23,9 +22,12 @@ def scatter_nd_impl(data, indices, updates):
output = np.copy(data)
for i in np.ndindex(indices.shape[:-1]):
# NOTE: The order of iteration in this loop is not specified.
# In particular, indices should not have duplicate entries: that is, if idx1 != idx2, then indices[idx1] != indices[idx2].
# This ensures that the output value does not depend on the iteration order.
output[indices[i]] = updates[i]
if reduction == 'add':
output[indices[i]] += updates[i]
elif reduction == 'mul':
output[indices[i]] *= updates[i]
else:
output[indices[i]] = updates[i]
return output
......@@ -55,3 +57,55 @@ class ScatterND(Base):
output = scatter_nd_impl(data, indices, updates)
expect(node, inputs=[data, indices, updates], outputs=[output],
name='test_scatternd')
@staticmethod
def export_scatternd_add(): # type: () -> None
node = onnx.helper.make_node(
'ScatterND',
inputs=['data', 'indices', 'updates'],
outputs=['y'],
reduction='add',
)
data = np.array(
[[[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]]], dtype=np.float32)
indices = np.array([[0], [0]], dtype=np.int64)
updates = np.array(
[[[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]]], dtype=np.float32)
# Expecting output as np.array(
# [[[7, 8, 9, 10], [13, 14, 15, 16], [18, 17, 16, 15], [16, 15, 14, 13]],
# [[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]]], dtype=np.float32)
output = scatter_nd_impl(data, indices, updates, reduction='add')
expect(node, inputs=[data, indices, updates], outputs=[output],
name='test_scatternd_add')
@staticmethod
def export_scatternd_multiply(): # type: () -> None
node = onnx.helper.make_node(
'ScatterND',
inputs=['data', 'indices', 'updates'],
outputs=['y'],
reduction='mul',
)
data = np.array(
[[[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]]], dtype=np.float32)
indices = np.array([[0], [0]], dtype=np.int64)
updates = np.array(
[[[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]]], dtype=np.float32)
# Expecting output as np.array(
# [[[5, 10, 15, 20], [60, 72, 84, 96], [168, 147, 126, 105], [128, 96, 64, 32]],
# [[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]]], dtype=np.float32)
output = scatter_nd_impl(data, indices, updates, reduction='mul')
expect(node, inputs=[data, indices, updates], outputs=[output],
name='test_scatternd_multiply')
 backend-test:á
N
data
indices
updatesy"ScatterElements*
axis *
reduction"add ,test_scatter_elements_with_duplicate_indicesZ
data


Z
indices


Z
updates


b
y


B
\ No newline at end of file
 backend-test:À
;
data
indices
updatesy" ScatterND*
reduction"add test_scatternd_addZ
data



Z
indices


Z
updates



b
y



B
\ No newline at end of file
 backend-test:
;
data
indices
updatesy" ScatterND*
reduction"multest_scatternd_multiplyZ
data



Z
indices


Z
updates



b
y



B
\ No newline at end of file
......@@ -990,8 +990,11 @@ class OpSet_Onnx_ver15 {
}
};
// Forward declarations for ai.onnx version 16
class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 16, RoiAlign);
class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 16, ScatterND);
class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 16, ScatterElements);
class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 16, If);
class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 16, Loop);
class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 16, Identity);
......@@ -1001,12 +1004,13 @@ class OpSet_Onnx_ver16 {
public:
static void ForEachSchema(std::function<void(OpSchema&&)> fn) {
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 16, RoiAlign)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 16, ScatterND)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 16, ScatterElements)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 16, If)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 16, Loop)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 16, Identity)>());
}
};
inline void RegisterOnnxOperatorSetSchema() {
RegisterOpSetSchema<OpSet_Onnx_ver1>();
RegisterOpSetSchema<OpSet_Onnx_ver2>();
......
......@@ -1302,7 +1302,7 @@ ONNX_OPERATOR_SET_SCHEMA(
}
}));
static const char* ScatterND_ver13_doc = R"DOC(
static const char* ScatterND_ver16_doc = R"DOC(
ScatterND takes three inputs `data` tensor of rank r >= 1, `indices` tensor of rank q >= 1,
and `updates` tensor of rank q + r - indices.shape[-1] - 1. The output of the operation
is produced by creating a copy of the input `data`, and then updating its value to values
......@@ -1335,6 +1335,24 @@ The order of iteration in the above loop is not specified.
In particular, indices should not have duplicate entries: that is, if idx1 != idx2, then indices[idx1] != indices[idx2].
This ensures that the output value does not depend on the iteration order.
`reduction` allows specification of an optional reduction operation, which is applied to all values in `updates`
tensor into `output` at the specified `indices`.
In cases where `reduction` is set to "none", indices should not have duplicate entries: that is, if idx1 != idx2,
then indices[idx1] != indices[idx2]. This ensures that the output value does not depend on the iteration order.
When `reduction` is set to "add", `output` is calculated as follows:
output = np.copy(data)
update_indices = indices.shape[:-1]
for idx in np.ndindex(update_indices):
output[indices[idx]] += updates[idx]
When `reduction` is set to "mul", `output` is calculated as follows:
output = np.copy(data)
update_indices = indices.shape[:-1]
for idx in np.ndindex(update_indices):
output[indices[idx]] *= updates[idx]
This operator is the inverse of GatherND.
Example 1:
......@@ -1363,9 +1381,17 @@ Example 2:
ONNX_OPERATOR_SET_SCHEMA(
ScatterND,
13,
16,
OpSchema()
.SetDoc(ScatterND_ver13_doc)
.SetDoc(ScatterND_ver16_doc)
.Attr(
"reduction",
"Type of reduction to apply: none (default), add, mul. "
"'none': no reduction applied. "
"'add': reduction using the addition operation. "
"'mul': reduction using the multiplication operation.",
AttributeProto::STRING,
std::string("none"))
.Input(
0,
"data",
......@@ -1413,7 +1439,7 @@ ONNX_OPERATOR_SET_SCHEMA(
}
}));
static const char* ScatterElements_ver13_doc = R"DOC(
static const char* ScatterElements_ver16_doc = R"DOC(
ScatterElements takes three inputs `data`, `updates`, and `indices` of the same
rank r >= 1 and an optional attribute axis that identifies an axis of `data`
(by default, the outer-most axis, that is axis 0). The output of the operation
......@@ -1427,12 +1453,25 @@ index-value for dimension = axis is obtained from the value of the corresponding
entry in `indices` and the index-value for dimension != axis is obtained from the
index of the entry itself.
For instance, in a 2-D tensor case, the update corresponding to the [i][j] entry
is performed as below:
`reduction` allows specification of an optional reduction operation, which is applied to all values in `updates`
tensor into `output` at the specified `indices`.
In cases where `reduction` is set to "none", indices should not have duplicate entries: that is, if idx1 != idx2,
then indices[idx1] != indices[idx2]. For instance, in a 2-D tensor case, the update
corresponding to the [i][j] entry is performed as below:
```
output[indices[i][j]][j] = updates[i][j] if axis = 0,
output[i][indices[i][j]] = updates[i][j] if axis = 1,
```
When `reduction` is set to "add", the update corresponding to the [i][j] entry is performed as below:
```
output[indices[i][j]][j] += updates[i][j] if axis = 0,
output[i][indices[i][j]] += updates[i][j] if axis = 1,
```
When `reduction` is set to "mul", the update corresponding to the [i][j] entry is performed as below:
```
output[indices[i][j]][j] *= updates[i][j] if axis = 0,
output[i][indices[i][j]] *= updates[i][j] if axis = 1,
```
This operator is the inverse of GatherElements. It is similar to Torch's Scatter operation.
......@@ -1469,15 +1508,23 @@ Example 2:
ONNX_OPERATOR_SET_SCHEMA(
ScatterElements,
13,
16,
OpSchema()
.SetDoc(ScatterElements_ver13_doc)
.SetDoc(ScatterElements_ver16_doc)
.Attr(
"axis",
"Which axis to scatter on. Negative value means "
"counting dimensions from the back. Accepted range is [-r, r-1] where r = rank(data).",
AttributeProto::INT,
static_cast<int64_t>(0))
.Attr(
"reduction",
"Type of reduction to apply: none (default), add, mul. "
"'none': no reduction applied. "
"'add': reduction using the addition operation. "
"'mul': reduction using the multiplication operation.",
AttributeProto::STRING,
std::string("none"))
.Input(
0,
"data",
......
......@@ -1007,6 +1007,117 @@ ONNX_OPERATOR_SET_SCHEMA(
}
}));
static const char* ScatterND_ver13_doc = R"DOC(
ScatterND takes three inputs `data` tensor of rank r >= 1, `indices` tensor of rank q >= 1,
and `updates` tensor of rank q + r - indices.shape[-1] - 1. The output of the operation
is produced by creating a copy of the input `data`, and then updating its value to values
specified by `updates` at specific index positions specified by `indices`. Its output shape
is the same as the shape of `data`. Note that `indices` should not have duplicate entries.
That is, two or more `updates` for the same index-location is not supported.
`indices` is an integer tensor. Let k denote indices.shape[-1], the last dimension in the shape of `indices`.
`indices` is treated as a (q-1)-dimensional tensor of k-tuples, where each k-tuple is a partial-index into `data`.
Hence, k can be a value at most the rank of `data`. When k equals rank(data), each update entry specifies an
update to a single element of the tensor. When k is less than rank(data) each update entry specifies an
update to a slice of the tensor.
`updates` is treated as a (q-1)-dimensional tensor of replacement-slice-values. Thus, the
first (q-1) dimensions of updates.shape must match the first (q-1) dimensions of indices.shape.
The remaining dimensions of `updates` correspond to the dimensions of the
replacement-slice-values. Each replacement-slice-value is a (r-k) dimensional tensor,
corresponding to the trailing (r-k) dimensions of `data`. Thus, the shape of `updates`
must equal indices.shape[0:q-1] ++ data.shape[k:r-1], where ++ denotes the concatenation
of shapes.
The `output` is calculated via the following equation:
output = np.copy(data)
update_indices = indices.shape[:-1]
for idx in np.ndindex(update_indices):
output[indices[idx]] = updates[idx]
The order of iteration in the above loop is not specified.
In particular, indices should not have duplicate entries: that is, if idx1 != idx2, then indices[idx1] != indices[idx2].
This ensures that the output value does not depend on the iteration order.
This operator is the inverse of GatherND.
Example 1:
```
data = [1, 2, 3, 4, 5, 6, 7, 8]
indices = [[4], [3], [1], [7]]
updates = [9, 10, 11, 12]
output = [1, 11, 3, 10, 9, 6, 7, 12]
```
Example 2:
```
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]]]
```
)DOC";
ONNX_OPERATOR_SET_SCHEMA(
ScatterND,
13,
OpSchema()
.SetDoc(ScatterND_ver13_doc)
.Input(
0,
"data",
"Tensor of rank r >= 1.",
"T",
OpSchema::Single,
true,
1,
OpSchema::Differentiable)
.Input(
1,
"indices",
"Tensor of rank q >= 1.",
"tensor(int64)",
OpSchema::Single,
true,
1,
OpSchema::NonDifferentiable)
.Input(
2,
"updates",
"Tensor of rank q + r - indices_shape[-1] - 1.",
"T",
OpSchema::Single,
true,
1,
OpSchema::Differentiable)
.Output(
0,
"output",
"Tensor of rank r >= 1.",
"T",
OpSchema::Single,
true,
1,
OpSchema::Differentiable)
.TypeConstraint(
"T",
OpSchema::all_tensor_types_with_bfloat(),
"Constrain input and output types to any tensor type.")
.TypeAndShapeInferenceFunction([](InferenceContext& ctx) {
propagateElemTypeFromInputToOutput(ctx, 0, 0);
if (hasNInputShapes(ctx, 1)) {
propagateShapeFromInputToOutput(ctx, 0, 0);
}
}));
static const char* ScatterND_ver11_doc = R"DOC(
ScatterND takes three inputs `data` tensor of rank r >= 1, `indices` tensor of rank q >= 1,
and `updates` tensor of rank q + r - indices.shape[-1] - 1. The output of the operation
......@@ -1090,6 +1201,123 @@ ONNX_OPERATOR_SET_SCHEMA(
}
}));
static const char* ScatterElements_ver13_doc = R"DOC(
ScatterElements takes three inputs `data`, `updates`, and `indices` of the same
rank r >= 1 and an optional attribute axis that identifies an axis of `data`
(by default, the outer-most axis, that is axis 0). The output of the operation
is produced by creating a copy of the input `data`, and then updating its value
to values specified by `updates` at specific index positions specified by
`indices`. Its output shape is the same as the shape of `data`.
For each entry in `updates`, the target index in `data` is obtained by combining
the corresponding entry in `indices` with the index of the entry itself: the
index-value for dimension = axis is obtained from the value of the corresponding
entry in `indices` and the index-value for dimension != axis is obtained from the
index of the entry itself.
For instance, in a 2-D tensor case, the update corresponding to the [i][j] entry
is performed as below:
```
output[indices[i][j]][j] = updates[i][j] if axis = 0,
output[i][indices[i][j]] = updates[i][j] if axis = 1,
```
This operator is the inverse of GatherElements. It is similar to Torch's Scatter operation.
Example 1:
```
data = [
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
]
indices = [
[1, 0, 2],
[0, 2, 1],
]
updates = [
[1.0, 1.1, 1.2],
[2.0, 2.1, 2.2],
]
output = [
[2.0, 1.1, 0.0]
[1.0, 0.0, 2.2]
[0.0, 2.1, 1.2]
]
```
Example 2:
```
data = [[1.0, 2.0, 3.0, 4.0, 5.0]]
indices = [[1, 3]]
updates = [[1.1, 2.1]]
axis = 1
output = [[1.0, 1.1, 3.0, 2.1, 5.0]]
```
)DOC";
ONNX_OPERATOR_SET_SCHEMA(
ScatterElements,
13,
OpSchema()
.SetDoc(ScatterElements_ver13_doc)
.Attr(
"axis",
"Which axis to scatter on. Negative value means "
"counting dimensions from the back. Accepted range is [-r, r-1] where r = rank(data).",
AttributeProto::INT,
static_cast<int64_t>(0))
.Input(
0,
"data",
"Tensor of rank r >= 1.",
"T",
OpSchema::Single,
true,
1,
OpSchema::Differentiable)
.Input(
1,
"indices",
"Tensor of int32/int64 indices, of r >= 1 (same rank as input). All index values are expected to be "
"within bounds [-s, s-1] along axis of size s. It is an error if any of the index values are out of bounds.",
"Tind",
OpSchema::Single,
true,
1,
OpSchema::NonDifferentiable)
.Input(
2,
"updates",
"Tensor of rank r >=1 (same rank and shape as indices)",
"T",
OpSchema::Single,
true,
1,
OpSchema::Differentiable)
.Output(
0,
"output",
"Tensor of rank r >= 1 (same rank as input).",
"T",
OpSchema::Single,
true,
1,
OpSchema::Differentiable)
.TypeConstraint(
"T",
OpSchema::all_tensor_types_with_bfloat(),
"Input and output types can be of any tensor type.")
.TypeConstraint(
"Tind",
{"tensor(int32)", "tensor(int64)"},
"Constrain indices to integer types")
.TypeAndShapeInferenceFunction([](InferenceContext& ctx) {
propagateElemTypeFromInputToOutput(ctx, 0, 0);
if (hasNInputShapes(ctx, 1)) {
propagateShapeFromInputToOutput(ctx, 0, 0);
}
}));
static const char* ScatterElements_ver11_doc = R"DOC(
ScatterElements takes three inputs `data`, `updates`, and `indices` of the same
rank r >= 1 and an optional attribute axis that identifies an axis of `data`
......
......@@ -841,18 +841,32 @@ class TestAutomaticUpgrade(unittest.TestCase):
[TensorProto.FLOAT]
)
def test_ScatterElements(self): # type: () -> None
def test_ScatterElements_1(self): # type: () -> None
self._test_op_upgrade('ScatterElements', 11, [[2, 3], [1, 2], [1, 2]], [[2, 3]],
[TensorProto.FLOAT, TensorProto.INT64, TensorProto.FLOAT],
[TensorProto.FLOAT]
)
def test_ScatterND(self): # type: () -> None
def test_ScatterElements_2(self): # type: () -> None
self._test_op_upgrade('ScatterElements', 16, [[2, 3], [1, 2], [1, 2]], [[2, 3]],
[TensorProto.FLOAT, TensorProto.INT64, TensorProto.FLOAT],
[TensorProto.FLOAT],
attrs={'reduction': 'add'}
)
def test_ScatterND_1(self): # type: () -> None
self._test_op_upgrade('ScatterND', 11, [[2, 3], [1, 2], [1, 2]], [[2, 3]],
[TensorProto.FLOAT, TensorProto.INT64, TensorProto.FLOAT],
[TensorProto.FLOAT]
)
def test_ScatterND_2(self): # type: () -> None
self._test_op_upgrade('ScatterND', 16, [[2, 3], [1, 2], [1, 2]], [[2, 3]],
[TensorProto.FLOAT, TensorProto.INT64, TensorProto.FLOAT],
[TensorProto.FLOAT],
attrs={'reduction': 'mul'}
)
def test_Scan(self): # type: () -> None
sum_in = onnx.helper.make_tensor_value_info('sum_in', onnx.TensorProto.FLOAT, [2])
next_in = onnx.helper.make_tensor_value_info('next_in', onnx.TensorProto.FLOAT, [2])
......
......@@ -758,6 +758,10 @@ class DefaultVersionConverter : public BaseVersionConverter {
/******** 15 -> 16 ********/
registerAdapter(make_unique<RoiAlign_15_16>());
registerAdapter(make_unique<CompatibleAdapter>(
"ScatterElements", OpSetID(15), OpSetID(16)));
registerAdapter(make_unique<CompatibleAdapter>(
"ScatterND", OpSetID(15), OpSetID(16)));
registerAdapter(make_unique<CompatibleAdapter>("Identity",
OpSetID(15), OpSetID(16)));
registerAdapter(make_unique<CompatibleAdapter>("Loop",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册