sync 91d990483a17
Browse files- README.md +13 -9
- build/webgpu/bench.json +0 -1
- build/webgpu/manifest.json +74 -162
- build/webgpu/metadata.json +14 -8
- build/webgpu/scatter-flat-copy.wgsl.jinja +30 -30
- build/webgpu/tensor-scatter.wgsl.jinja +40 -31
- build/webgpu/test.json +50 -3
README.md
CHANGED
|
@@ -18,17 +18,17 @@ See the [ONNX `TensorScatter` spec](https://onnx.ai/onnx/operators/onnx__TensorS
|
|
| 18 |
|
| 19 |
## Inputs
|
| 20 |
|
| 21 |
-
| Name |
|
| 22 |
| --- | --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
-
| `
|
| 24 |
-
| `update` |
|
| 25 |
-
| `
|
| 26 |
|
| 27 |
## Outputs
|
| 28 |
|
| 29 |
-
| Name |
|
| 30 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 31 |
-
| `
|
| 32 |
|
| 33 |
## Attributes
|
| 34 |
|
|
@@ -48,7 +48,7 @@ Default values (overridable per request):
|
|
| 48 |
|
| 49 |
## Files
|
| 50 |
|
| 51 |
-
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
|
| 52 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 53 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 54 |
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
|
@@ -57,10 +57,14 @@ Default values (overridable per request):
|
|
| 57 |
|
| 58 |
## Use with `@huggingface/kernels`
|
| 59 |
|
| 60 |
-
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
|
|
|
| 64 |
|
| 65 |
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 66 |
|
|
|
|
| 18 |
|
| 19 |
## Inputs
|
| 20 |
|
| 21 |
+
| Name | Upstream name | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
|
| 22 |
| --- | --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `past` | `past_cache` | `T` | runtime-selected; narrow integers and bool use 32-bit slots | — | — | Existing cache tensor with shape `(batch_size, ..., max_sequence_length, ...)`. | required |
|
| 24 |
+
| `update` | — | `T` | runtime-selected; narrow integers and bool use 32-bit slots | — | — | New values to scatter in, with the same shape as `past_cache` except the sequence dimension equals `sequence_length`. | required |
|
| 25 |
+
| `writeIndices` | `write_indices` | `I` | `uint32` | `1` | — | Logical int64 per-sample write offset into the cache sequence dimension; shape `(batch_size,)`, stored as uint32 by WebGPU, and assumed all zeros if absent. | optional |
|
| 26 |
|
| 27 |
## Outputs
|
| 28 |
|
| 29 |
+
| Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
|
| 30 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 31 |
+
| `present` | `present_cache` | `T` | same as `past` | same as `past` | Updated cache; same shape as `past_cache`. | required |
|
| 32 |
|
| 33 |
## Attributes
|
| 34 |
|
|
|
|
| 48 |
|
| 49 |
## Files
|
| 50 |
|
| 51 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
|
| 52 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 53 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 54 |
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
|
|
|
| 57 |
|
| 58 |
## Use with `@huggingface/kernels`
|
| 59 |
|
| 60 |
+
```sh
|
| 61 |
+
npm install --save-exact @huggingface/kernels@0.0.1-preview.2
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
|
| 65 |
|
| 66 |
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 67 |
+
It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
|
| 68 |
|
| 69 |
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 70 |
|
build/webgpu/bench.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
{
|
| 2 |
-
"op": "ai.onnx.TensorScatter",
|
| 3 |
"cases": [
|
| 4 |
{
|
| 5 |
"name": "decode-kv-update-f32",
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"cases": [
|
| 3 |
{
|
| 4 |
"name": "decode-kv-update-f32",
|
build/webgpu/manifest.json
CHANGED
|
@@ -2,135 +2,39 @@
|
|
| 2 |
"domain": "ai.onnx",
|
| 3 |
"name": "TensorScatter",
|
| 4 |
"sinceVersion": 24,
|
| 5 |
-
"
|
| 6 |
-
|
| 7 |
-
{
|
| 8 |
-
|
| 9 |
-
"
|
| 10 |
-
"description": "Existing cache tensor with shape `(batch_size, ..., max_sequence_length, ...)`."
|
| 11 |
-
},
|
| 12 |
-
{
|
| 13 |
-
"role": "update",
|
| 14 |
-
"dtype": "T",
|
| 15 |
-
"description": "New values to scatter in, with the same shape as `past_cache` except the sequence dimension equals `sequence_length`."
|
| 16 |
-
},
|
| 17 |
-
{
|
| 18 |
-
"role": "write_indices",
|
| 19 |
"dtype": "I",
|
| 20 |
"rank": 1,
|
| 21 |
"optional": true,
|
| 22 |
-
"
|
| 23 |
-
|
| 24 |
-
],
|
| 25 |
-
"outputs": [
|
| 26 |
-
{
|
| 27 |
-
"role": "present_cache",
|
| 28 |
-
"dtype": "T",
|
| 29 |
-
"rank": "ranks.past_cache",
|
| 30 |
-
"description": "Updated cache; same shape as `past_cache`.",
|
| 31 |
-
"shape": "shapes.past_cache"
|
| 32 |
}
|
| 33 |
-
],
|
| 34 |
-
"attributes": { "axis": -2, "mode": "linear" },
|
| 35 |
-
"attributeDescriptions": {
|
| 36 |
-
"axis": "Sequence dimension of `past_cache` and `update`; cannot be 0 (the batch dimension). Default is `-2`.",
|
| 37 |
-
"mode": "Write mode: `linear` requires `write_indices + sequence_length <= max_sequence_length`; `circular` wraps the write index modulo `max_sequence_length`."
|
| 38 |
},
|
|
|
|
|
|
|
| 39 |
"attributeConstraints": { "mode": { "values": ["linear", "circular"] } },
|
| 40 |
"typeConstraints": {
|
| 41 |
"T": ["float32", "float16", "int32", "int16", "int8", "uint32", "uint8", "bool"],
|
| 42 |
"I": ["int64"]
|
| 43 |
},
|
| 44 |
-
"
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
"
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
"dtype": "uint32",
|
| 52 |
-
"narrowing": "checked",
|
| 53 |
-
"required": false
|
| 54 |
-
},
|
| 55 |
-
"present": { "kind": "tensor", "semantic": "present_cache", "role": "output" }
|
| 56 |
-
},
|
| 57 |
-
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 58 |
-
"derive": {},
|
| 59 |
-
"bindingSets": {
|
| 60 |
-
"copyCache": [
|
| 61 |
-
{
|
| 62 |
-
"name": "src",
|
| 63 |
-
"arg": "past",
|
| 64 |
-
"semantic": "past_cache",
|
| 65 |
-
"buffer": { "type": "read-only-storage" },
|
| 66 |
-
"elementType": "$copyElementType"
|
| 67 |
-
},
|
| 68 |
-
{
|
| 69 |
-
"name": "dst",
|
| 70 |
-
"arg": "present",
|
| 71 |
-
"semantic": "present_cache",
|
| 72 |
-
"buffer": { "type": "storage" },
|
| 73 |
-
"elementType": "$copyElementType"
|
| 74 |
-
}
|
| 75 |
-
],
|
| 76 |
-
"contiguousUpdate": [
|
| 77 |
-
{
|
| 78 |
-
"name": "update",
|
| 79 |
-
"arg": "update",
|
| 80 |
-
"semantic": "update",
|
| 81 |
-
"buffer": { "type": "read-only-storage" },
|
| 82 |
-
"elementType": "$scalar"
|
| 83 |
-
},
|
| 84 |
-
{
|
| 85 |
-
"name": "present",
|
| 86 |
-
"arg": "present",
|
| 87 |
-
"semantic": "present_cache",
|
| 88 |
-
"buffer": { "type": "storage" },
|
| 89 |
-
"elementType": "$scalar"
|
| 90 |
-
},
|
| 91 |
-
{
|
| 92 |
-
"name": "params",
|
| 93 |
-
"semantic": "kernel.params",
|
| 94 |
-
"buffer": { "type": "uniform" },
|
| 95 |
-
"struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.update)" }] }
|
| 96 |
-
}
|
| 97 |
-
],
|
| 98 |
-
"indexedUpdate": [
|
| 99 |
-
{
|
| 100 |
-
"name": "update",
|
| 101 |
-
"arg": "update",
|
| 102 |
-
"semantic": "update",
|
| 103 |
-
"buffer": { "type": "read-only-storage" },
|
| 104 |
-
"elementType": "$scalar"
|
| 105 |
-
},
|
| 106 |
-
{
|
| 107 |
-
"name": "writeIndices",
|
| 108 |
-
"arg": "writeIndices",
|
| 109 |
-
"semantic": "write_indices",
|
| 110 |
-
"buffer": { "type": "read-only-storage" },
|
| 111 |
-
"elementType": "u32"
|
| 112 |
-
},
|
| 113 |
-
{
|
| 114 |
-
"name": "present",
|
| 115 |
-
"arg": "present",
|
| 116 |
-
"semantic": "present_cache",
|
| 117 |
-
"buffer": { "type": "storage" },
|
| 118 |
-
"elementType": "$scalar"
|
| 119 |
-
},
|
| 120 |
-
{
|
| 121 |
-
"name": "params",
|
| 122 |
-
"semantic": "kernel.params",
|
| 123 |
-
"buffer": { "type": "uniform" },
|
| 124 |
-
"struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.update)" }] }
|
| 125 |
-
}
|
| 126 |
-
]
|
| 127 |
},
|
| 128 |
"variants": [
|
| 129 |
{
|
| 130 |
"id": "no_write_indices",
|
| 131 |
"priority": 0,
|
| 132 |
-
"when": ["not present.writeIndices"
|
| 133 |
-
"
|
| 134 |
"scalar": "dtypes.T",
|
| 135 |
"usesF16": "dtypes.T == \"f16\"",
|
| 136 |
"copyElementType": "(\"vec4<\" ~ dtypes.T ~ \">\") if numel(shapes.present) % 4 == 0 else dtypes.T"
|
|
@@ -139,46 +43,50 @@
|
|
| 139 |
{
|
| 140 |
"id": "copy",
|
| 141 |
"name": "TensorScatter.Copy",
|
| 142 |
-
"
|
| 143 |
-
|
| 144 |
-
"
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
"scalarTailSafe": "numel(shapes.present) % 4 != 0"
|
| 149 |
-
}
|
| 150 |
},
|
| 151 |
-
"bindings":
|
|
|
|
|
|
|
|
|
|
| 152 |
"dispatch": {
|
| 153 |
-
"
|
| 154 |
-
"
|
|
|
|
| 155 |
},
|
| 156 |
"viewAlias": [{ "input": "src", "output": "dst" }]
|
| 157 |
},
|
| 158 |
{
|
| 159 |
"id": "scatter",
|
| 160 |
"name": "TensorScatter.Scatter",
|
| 161 |
-
"
|
| 162 |
-
|
| 163 |
-
"
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
"hasIndices": "false"
|
| 170 |
-
}
|
| 171 |
},
|
| 172 |
-
"bindings": "
|
| 173 |
-
"dispatch": {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
}
|
| 175 |
]
|
| 176 |
},
|
| 177 |
{
|
| 178 |
"id": "with_write_indices",
|
| 179 |
"priority": 20,
|
| 180 |
-
"when": ["present.writeIndices", "dtypes.I == \"u32\""
|
| 181 |
-
"
|
| 182 |
"scalar": "dtypes.T",
|
| 183 |
"usesF16": "dtypes.T == \"f16\"",
|
| 184 |
"copyElementType": "(\"vec4<\" ~ dtypes.T ~ \">\") if numel(shapes.present) % 4 == 0 else dtypes.T"
|
|
@@ -187,38 +95,42 @@
|
|
| 187 |
{
|
| 188 |
"id": "copy",
|
| 189 |
"name": "TensorScatter.Copy",
|
| 190 |
-
"
|
| 191 |
-
|
| 192 |
-
"
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
"scalarTailSafe": "numel(shapes.present) % 4 != 0"
|
| 197 |
-
}
|
| 198 |
},
|
| 199 |
-
"bindings":
|
|
|
|
|
|
|
|
|
|
| 200 |
"dispatch": {
|
| 201 |
-
"
|
| 202 |
-
"
|
|
|
|
| 203 |
},
|
| 204 |
"viewAlias": [{ "input": "src", "output": "dst" }]
|
| 205 |
},
|
| 206 |
{
|
| 207 |
"id": "scatter",
|
| 208 |
"name": "TensorScatter.Scatter",
|
| 209 |
-
"
|
| 210 |
-
|
| 211 |
-
"
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
"hasIndices": "true"
|
| 218 |
-
}
|
| 219 |
},
|
| 220 |
-
"bindings": "
|
| 221 |
-
"dispatch": {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
}
|
| 223 |
]
|
| 224 |
}
|
|
|
|
| 2 |
"domain": "ai.onnx",
|
| 3 |
"name": "TensorScatter",
|
| 4 |
"sinceVersion": 24,
|
| 5 |
+
"inputs": {
|
| 6 |
+
"past": { "onnx": "past_cache", "dtype": "T" },
|
| 7 |
+
"update": { "dtype": "T" },
|
| 8 |
+
"writeIndices": {
|
| 9 |
+
"onnx": "write_indices",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
"dtype": "I",
|
| 11 |
"rank": 1,
|
| 12 |
"optional": true,
|
| 13 |
+
"storage": "uint32",
|
| 14 |
+
"narrowing": "checked"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
},
|
| 17 |
+
"outputs": { "present": { "onnx": "present_cache", "dtype": "T", "rank": "ranks.past", "shape": "shapes.past" } },
|
| 18 |
+
"attributes": { "axis": { "default": -2 }, "mode": { "default": "linear" } },
|
| 19 |
"attributeConstraints": { "mode": { "values": ["linear", "circular"] } },
|
| 20 |
"typeConstraints": {
|
| 21 |
"T": ["float32", "float16", "int32", "int16", "int8", "uint32", "uint8", "bool"],
|
| 22 |
"I": ["int64"]
|
| 23 |
},
|
| 24 |
+
"tunables": { "WORKGROUP_SIZE": { "default": 256 } },
|
| 25 |
+
"when": ["(attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.past) < ranks.past", "(attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.past) >= 1", "f16Ok(dtypes.T)", "numel(shapes.past) == numel(shapes.present)", "ranks.past == ranks.present", "ranks.past == ranks.update", "ranks.past >= 2"],
|
| 26 |
+
"bindings": {
|
| 27 |
+
"update": { "buffer": "read-only-storage", "elementType": "$scalar" },
|
| 28 |
+
"present": { "buffer": "storage", "elementType": "$scalar" },
|
| 29 |
+
"params": { "buffer": "uniform", "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.update)" }] },
|
| 30 |
+
"writeIndices": { "buffer": "read-only-storage", "elementType": "u32" }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
},
|
| 32 |
"variants": [
|
| 33 |
{
|
| 34 |
"id": "no_write_indices",
|
| 35 |
"priority": 0,
|
| 36 |
+
"when": ["not present.writeIndices"],
|
| 37 |
+
"derive": {
|
| 38 |
"scalar": "dtypes.T",
|
| 39 |
"usesF16": "dtypes.T == \"f16\"",
|
| 40 |
"copyElementType": "(\"vec4<\" ~ dtypes.T ~ \">\") if numel(shapes.present) % 4 == 0 else dtypes.T"
|
|
|
|
| 43 |
{
|
| 44 |
"id": "copy",
|
| 45 |
"name": "TensorScatter.Copy",
|
| 46 |
+
"shader": "scatter-flat-copy.wgsl.jinja",
|
| 47 |
+
"derive": {
|
| 48 |
+
"count": "ceilDiv(numel(shapes.present), 16 if dtypes.T == \"f16\" and numel(shapes.present) % 4 != 0 else (8 if numel(shapes.present) % 4 != 0 else 4))",
|
| 49 |
+
"copyWidth": "16 if dtypes.T == \"f16\" and numel(shapes.present) % 4 != 0 else (8 if numel(shapes.present) % 4 != 0 else 4)",
|
| 50 |
+
"elementCount": "numel(shapes.present)",
|
| 51 |
+
"scalarTailSafe": "numel(shapes.present) % 4 != 0"
|
|
|
|
|
|
|
| 52 |
},
|
| 53 |
+
"bindings": [
|
| 54 |
+
{ "arg": "past", "name": "src", "elementType": "$copyElementType" },
|
| 55 |
+
{ "arg": "present", "name": "dst", "elementType": "$copyElementType" }
|
| 56 |
+
],
|
| 57 |
"dispatch": {
|
| 58 |
+
"x": "min(ceilDiv((ceilDiv(numel(shapes.present), 16 if dtypes.T == \"f16\" and numel(shapes.present) % 4 != 0 else (8 if numel(shapes.present) % 4 != 0 else 4))), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 59 |
+
"y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.present), 16 if dtypes.T == \"f16\" and numel(shapes.present) % 4 != 0 else (8 if numel(shapes.present) % 4 != 0 else 4))), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 60 |
+
"z": 1
|
| 61 |
},
|
| 62 |
"viewAlias": [{ "input": "src", "output": "dst" }]
|
| 63 |
},
|
| 64 |
{
|
| 65 |
"id": "scatter",
|
| 66 |
"name": "TensorScatter.Scatter",
|
| 67 |
+
"shader": "tensor-scatter.wgsl.jinja",
|
| 68 |
+
"derive": {
|
| 69 |
+
"pastShape": "shapes.past",
|
| 70 |
+
"updateShape": "shapes.update",
|
| 71 |
+
"rank": "ranks.past",
|
| 72 |
+
"axisSpec": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.past",
|
| 73 |
+
"circular": "attrs.mode == \"circular\"",
|
| 74 |
+
"hasIndices": "false"
|
|
|
|
|
|
|
| 75 |
},
|
| 76 |
+
"bindings": ["update", "present", "params"],
|
| 77 |
+
"dispatch": {
|
| 78 |
+
"x": "min(ceilDiv((numel(shapes.update)), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 79 |
+
"y": "ceilDiv(ceilDiv((numel(shapes.update)), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 80 |
+
"z": 1
|
| 81 |
+
}
|
| 82 |
}
|
| 83 |
]
|
| 84 |
},
|
| 85 |
{
|
| 86 |
"id": "with_write_indices",
|
| 87 |
"priority": 20,
|
| 88 |
+
"when": ["present.writeIndices", "dtypes.I == \"u32\""],
|
| 89 |
+
"derive": {
|
| 90 |
"scalar": "dtypes.T",
|
| 91 |
"usesF16": "dtypes.T == \"f16\"",
|
| 92 |
"copyElementType": "(\"vec4<\" ~ dtypes.T ~ \">\") if numel(shapes.present) % 4 == 0 else dtypes.T"
|
|
|
|
| 95 |
{
|
| 96 |
"id": "copy",
|
| 97 |
"name": "TensorScatter.Copy",
|
| 98 |
+
"shader": "scatter-flat-copy.wgsl.jinja",
|
| 99 |
+
"derive": {
|
| 100 |
+
"count": "ceilDiv(numel(shapes.present), 16 if dtypes.T == \"f16\" and numel(shapes.present) % 4 != 0 else (8 if numel(shapes.present) % 4 != 0 else 4))",
|
| 101 |
+
"copyWidth": "16 if dtypes.T == \"f16\" and numel(shapes.present) % 4 != 0 else (8 if numel(shapes.present) % 4 != 0 else 4)",
|
| 102 |
+
"elementCount": "numel(shapes.present)",
|
| 103 |
+
"scalarTailSafe": "numel(shapes.present) % 4 != 0"
|
|
|
|
|
|
|
| 104 |
},
|
| 105 |
+
"bindings": [
|
| 106 |
+
{ "arg": "past", "name": "src", "elementType": "$copyElementType" },
|
| 107 |
+
{ "arg": "present", "name": "dst", "elementType": "$copyElementType" }
|
| 108 |
+
],
|
| 109 |
"dispatch": {
|
| 110 |
+
"x": "min(ceilDiv((ceilDiv(numel(shapes.present), 16 if dtypes.T == \"f16\" and numel(shapes.present) % 4 != 0 else (8 if numel(shapes.present) % 4 != 0 else 4))), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 111 |
+
"y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.present), 16 if dtypes.T == \"f16\" and numel(shapes.present) % 4 != 0 else (8 if numel(shapes.present) % 4 != 0 else 4))), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 112 |
+
"z": 1
|
| 113 |
},
|
| 114 |
"viewAlias": [{ "input": "src", "output": "dst" }]
|
| 115 |
},
|
| 116 |
{
|
| 117 |
"id": "scatter",
|
| 118 |
"name": "TensorScatter.Scatter",
|
| 119 |
+
"shader": "tensor-scatter.wgsl.jinja",
|
| 120 |
+
"derive": {
|
| 121 |
+
"pastShape": "shapes.past",
|
| 122 |
+
"updateShape": "shapes.update",
|
| 123 |
+
"rank": "ranks.past",
|
| 124 |
+
"axisSpec": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.past",
|
| 125 |
+
"circular": "attrs.mode == \"circular\"",
|
| 126 |
+
"hasIndices": "true"
|
|
|
|
|
|
|
| 127 |
},
|
| 128 |
+
"bindings": ["update", "writeIndices", "present", "params"],
|
| 129 |
+
"dispatch": {
|
| 130 |
+
"x": "min(ceilDiv((numel(shapes.update)), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 131 |
+
"y": "ceilDiv(ceilDiv((numel(shapes.update)), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 132 |
+
"z": 1
|
| 133 |
+
}
|
| 134 |
}
|
| 135 |
]
|
| 136 |
}
|
build/webgpu/metadata.json
CHANGED
|
@@ -1,19 +1,25 @@
|
|
| 1 |
{
|
| 2 |
"name": "ai.onnx.TensorScatter",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
-
"bench.json": "
|
| 11 |
-
"manifest.json": "
|
| 12 |
-
"scatter-flat-copy.wgsl.jinja": "
|
| 13 |
-
"tensor-scatter.wgsl.jinja": "
|
| 14 |
-
"test.json": "
|
| 15 |
}
|
| 16 |
},
|
| 17 |
-
"provenance": { "kernel": { "sha": "
|
| 18 |
-
"webgpu": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "ai.onnx.TensorScatter",
|
| 3 |
+
"id": "_ai_onnx_tensorscatter_webgpu_fae7a8d",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
+
"bench.json": "8pIi4/PPMqyk12pePykM6uwawjM0N6tsexFwiHFqySw=",
|
| 11 |
+
"manifest.json": "RY7RXbSuGDdCHSr8e4qqS5wG4oCULwzNV5C9/ToI6Ak=",
|
| 12 |
+
"scatter-flat-copy.wgsl.jinja": "mXcfAXV6Nxe+Y+cvem8Wwwt0BeqRQXaiCNhQoiynX0M=",
|
| 13 |
+
"tensor-scatter.wgsl.jinja": "cDtR3E7EOm41/YKXMPKfPP3YCakYj/pbU85wt2obnY0=",
|
| 14 |
+
"test.json": "H/VHqdsOkO3A3dM1LSKrAmk5XO2vlrDyry0EXKm/2X4="
|
| 15 |
}
|
| 16 |
},
|
| 17 |
+
"provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
|
| 18 |
+
"webgpu": {
|
| 19 |
+
"manifestSpec": "2.0",
|
| 20 |
+
"variants": {
|
| 21 |
+
"no_write_indices": ["scatter-flat-copy.wgsl.jinja", "tensor-scatter.wgsl.jinja"],
|
| 22 |
+
"with_write_indices": ["scatter-flat-copy.wgsl.jinja", "tensor-scatter.wgsl.jinja"]
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
}
|
build/webgpu/scatter-flat-copy.wgsl.jinja
CHANGED
|
@@ -2,46 +2,46 @@
|
|
| 2 |
// pass. The element width is owned by the binding element type: when
|
| 3 |
// numel(output) % 4 == 0 the bindings rebind as vec4<T> (128-bit loads).
|
| 4 |
// Tail-safe modes keep scalar bindings but copy a compile-time group of adjacent
|
| 5 |
-
// elements per invocation, guarding only the final partial group.
|
| 6 |
-
|
|
|
|
|
|
|
| 7 |
{% if usesF16 %}
|
| 8 |
enable f16;
|
| 9 |
{% endif %}
|
| 10 |
{{ env.wgsl.resourceDeclarations }}
|
| 11 |
{% set wg = workgroupSize if workgroupSize is defined else tunables.WORKGROUP_SIZE %}
|
| 12 |
|
| 13 |
-
const COUNT: u32 = {{
|
| 14 |
const WG: u32 = {{ wg }}u;
|
| 15 |
|
| 16 |
@compute @workgroup_size({{ wg }})
|
| 17 |
-
fn main(
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
{
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
if (base + 15u < {{ source.elementCount }}u) { dst[base + 15u] = src[base + 15u]; }
|
| 42 |
{% endif %}
|
| 43 |
{% else %}
|
| 44 |
-
|
| 45 |
{% endif %}
|
| 46 |
-
}
|
| 47 |
}
|
|
|
|
| 2 |
// pass. The element width is owned by the binding element type: when
|
| 3 |
// numel(output) % 4 == 0 the bindings rebind as vec4<T> (128-bit loads).
|
| 4 |
// Tail-safe modes keep scalar bindings but copy a compile-time group of adjacent
|
| 5 |
+
// elements per invocation, guarding only the final partial group.
|
| 6 |
+
{% set scalarTailSafe = scalarTailSafe is defined and scalarTailSafe %}
|
| 7 |
+
{% set copyWidth = copyWidth | default(0) %}
|
| 8 |
+
{% set elementCount = elementCount | default(0) %}
|
| 9 |
{% if usesF16 %}
|
| 10 |
enable f16;
|
| 11 |
{% endif %}
|
| 12 |
{{ env.wgsl.resourceDeclarations }}
|
| 13 |
{% set wg = workgroupSize if workgroupSize is defined else tunables.WORKGROUP_SIZE %}
|
| 14 |
|
| 15 |
+
const COUNT: u32 = {{ count }}u;
|
| 16 |
const WG: u32 = {{ wg }}u;
|
| 17 |
|
| 18 |
@compute @workgroup_size({{ wg }})
|
| 19 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 20 |
+
let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
|
| 21 |
+
if (i >= COUNT) {
|
| 22 |
+
return;
|
| 23 |
+
}
|
| 24 |
+
{% if scalarTailSafe %}
|
| 25 |
+
let base = i * {{ copyWidth }}u;
|
| 26 |
+
dst[base] = src[base];
|
| 27 |
+
if (base + 1u < {{ elementCount }}u) { dst[base + 1u] = src[base + 1u]; }
|
| 28 |
+
if (base + 2u < {{ elementCount }}u) { dst[base + 2u] = src[base + 2u]; }
|
| 29 |
+
if (base + 3u < {{ elementCount }}u) { dst[base + 3u] = src[base + 3u]; }
|
| 30 |
+
if (base + 4u < {{ elementCount }}u) { dst[base + 4u] = src[base + 4u]; }
|
| 31 |
+
if (base + 5u < {{ elementCount }}u) { dst[base + 5u] = src[base + 5u]; }
|
| 32 |
+
if (base + 6u < {{ elementCount }}u) { dst[base + 6u] = src[base + 6u]; }
|
| 33 |
+
if (base + 7u < {{ elementCount }}u) { dst[base + 7u] = src[base + 7u]; }
|
| 34 |
+
{% if copyWidth == 16 %}
|
| 35 |
+
if (base + 8u < {{ elementCount }}u) { dst[base + 8u] = src[base + 8u]; }
|
| 36 |
+
if (base + 9u < {{ elementCount }}u) { dst[base + 9u] = src[base + 9u]; }
|
| 37 |
+
if (base + 10u < {{ elementCount }}u) { dst[base + 10u] = src[base + 10u]; }
|
| 38 |
+
if (base + 11u < {{ elementCount }}u) { dst[base + 11u] = src[base + 11u]; }
|
| 39 |
+
if (base + 12u < {{ elementCount }}u) { dst[base + 12u] = src[base + 12u]; }
|
| 40 |
+
if (base + 13u < {{ elementCount }}u) { dst[base + 13u] = src[base + 13u]; }
|
| 41 |
+
if (base + 14u < {{ elementCount }}u) { dst[base + 14u] = src[base + 14u]; }
|
| 42 |
+
if (base + 15u < {{ elementCount }}u) { dst[base + 15u] = src[base + 15u]; }
|
|
|
|
| 43 |
{% endif %}
|
| 44 |
{% else %}
|
| 45 |
+
dst[i] = src[i];
|
| 46 |
{% endif %}
|
|
|
|
| 47 |
}
|
build/webgpu/tensor-scatter.wgsl.jinja
CHANGED
|
@@ -1,63 +1,60 @@
|
|
| 1 |
{% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
|
| 2 |
{% if note == "dispatch-limit" %}
|
| 3 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 4 |
-
//
|
| 5 |
{% elif note == "limit" %}
|
| 6 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 7 |
-
//
|
| 8 |
{% elif note == "device-axis" %}
|
| 9 |
-
// The flat dispatch is folded across x/y at
|
| 10 |
-
//
|
| 11 |
{% elif note == "vec4-limit" %}
|
| 12 |
-
// 2D-folded flat vec4 index: gid.y carries the high bits past the
|
| 13 |
-
//
|
| 14 |
{% elif note == "element-limit" %}
|
| 15 |
// 2D-folded flat element index: gid.y carries the high bits past the
|
| 16 |
-
//
|
| 17 |
{% elif note == "dispatch" %}
|
| 18 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 19 |
-
//
|
| 20 |
{% endif %}
|
| 21 |
{% if bound == "" %}
|
| 22 |
-
let {{ name }} = gid.x + gid.y *
|
| 23 |
{%- elif guardInline %}
|
| 24 |
-
let {{ name }} = gid.x + gid.y *
|
| 25 |
if ({{ name }} >= {{ bound }}) { return; }
|
| 26 |
{%- else %}
|
| 27 |
-
let {{ name }} = gid.x + gid.y *
|
| 28 |
if ({{ name }} >= {{ bound }}) {
|
| 29 |
return;
|
| 30 |
}
|
| 31 |
{%- endif %}
|
| 32 |
{% endmacro %}
|
| 33 |
|
| 34 |
-
{% if usesF16 %}
|
| 35 |
-
enable f16;
|
| 36 |
-
{% endif %}
|
| 37 |
{{ env.wgsl.resourceDeclarations }}
|
| 38 |
|
| 39 |
-
{% set max_seq =
|
| 40 |
-
{% set seq_len =
|
| 41 |
{% set dim0_stride = namespace(value=1) %}
|
| 42 |
-
{% for j in range(1,
|
| 43 |
{% set axis_stride = namespace(value=1) %}
|
| 44 |
-
{% for j in range(
|
| 45 |
|
| 46 |
// Map a flat index into the `update` tensor onto a flat index into `present`,
|
| 47 |
// using `cache_axis` as the sequence-axis coordinate.
|
| 48 |
fn present_offset(update_index: u32, cache_axis: u32) -> u32 {
|
| 49 |
var rem = update_index;
|
| 50 |
var offset = 0u;
|
| 51 |
-
{% for d in range(
|
| 52 |
{% set u_stride = namespace(value=1) %}
|
| 53 |
-
{% for j in range(d + 1,
|
| 54 |
{% set p_stride = namespace(value=1) %}
|
| 55 |
-
{% for j in range(d + 1,
|
| 56 |
-
{% if d !=
|
| 57 |
let coord{{ d }} = rem / {{ max(u_stride.value, 1) }}u;
|
| 58 |
{% endif %}
|
| 59 |
rem = rem % {{ max(u_stride.value, 1) }}u;
|
| 60 |
-
{% if d ==
|
| 61 |
offset = offset + cache_axis * {{ p_stride.value }}u;
|
| 62 |
{% else %}
|
| 63 |
offset = offset + coord{{ d }} * {{ p_stride.value }}u;
|
|
@@ -67,21 +64,33 @@ fn present_offset(update_index: u32, cache_axis: u32) -> u32 {
|
|
| 67 |
}
|
| 68 |
|
| 69 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 70 |
-
fn main(@builtin(global_invocation_id) gid: vec3<u32>
|
| 71 |
{{ flat_index_2d("j") }}
|
| 72 |
// batch_idx is the leading (dim-0) coordinate; s is the coordinate along the
|
| 73 |
// sequence axis. write_indices is indexed per batch sample.
|
| 74 |
-
{% if
|
| 75 |
let batch_idx = j / {{ max(dim0_stride.value, 1) }}u;
|
| 76 |
{% endif %}
|
| 77 |
let s = (j / {{ max(axis_stride.value, 1) }}u) % {{ max(seq_len, 1) }}u;
|
| 78 |
-
{% if
|
| 79 |
let w = writeIndices[batch_idx];
|
| 80 |
{% else %}
|
| 81 |
let w = 0u;
|
| 82 |
{% endif %}
|
| 83 |
-
{% if
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
{% else %}
|
| 86 |
let cache_axis = w + s;
|
| 87 |
{% endif %}
|
|
|
|
| 1 |
{% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
|
| 2 |
{% if note == "dispatch-limit" %}
|
| 3 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 4 |
+
// per-axis workgroup fold width (outputs > 16.7M elements).
|
| 5 |
{% elif note == "limit" %}
|
| 6 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 7 |
+
// per-axis workgroup fold width.
|
| 8 |
{% elif note == "device-axis" %}
|
| 9 |
+
// The flat dispatch is folded across x/y at a fixed per-axis workgroup
|
| 10 |
+
// width; gid.y carries the high portion of the output index.
|
| 11 |
{% elif note == "vec4-limit" %}
|
| 12 |
+
// 2D-folded flat vec4 index: gid.y carries the high bits past the dispatch's
|
| 13 |
+
// per-axis workgroup fold width (the dispatch caps x and spills into y).
|
| 14 |
{% elif note == "element-limit" %}
|
| 15 |
// 2D-folded flat element index: gid.y carries the high bits past the
|
| 16 |
+
// dispatch's per-axis workgroup fold width.
|
| 17 |
{% elif note == "dispatch" %}
|
| 18 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 19 |
+
// per-axis workgroup fold width.
|
| 20 |
{% endif %}
|
| 21 |
{% if bound == "" %}
|
| 22 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
|
| 23 |
{%- elif guardInline %}
|
| 24 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
|
| 25 |
if ({{ name }} >= {{ bound }}) { return; }
|
| 26 |
{%- else %}
|
| 27 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
|
| 28 |
if ({{ name }} >= {{ bound }}) {
|
| 29 |
return;
|
| 30 |
}
|
| 31 |
{%- endif %}
|
| 32 |
{% endmacro %}
|
| 33 |
|
|
|
|
|
|
|
|
|
|
| 34 |
{{ env.wgsl.resourceDeclarations }}
|
| 35 |
|
| 36 |
+
{% set max_seq = pastShape[axisSpec] %}
|
| 37 |
+
{% set seq_len = updateShape[axisSpec] %}
|
| 38 |
{% set dim0_stride = namespace(value=1) %}
|
| 39 |
+
{% for j in range(1, rank) %}{% set dim0_stride.value = dim0_stride.value * updateShape[j] %}{% endfor %}
|
| 40 |
{% set axis_stride = namespace(value=1) %}
|
| 41 |
+
{% for j in range(axisSpec + 1, rank) %}{% set axis_stride.value = axis_stride.value * updateShape[j] %}{% endfor %}
|
| 42 |
|
| 43 |
// Map a flat index into the `update` tensor onto a flat index into `present`,
|
| 44 |
// using `cache_axis` as the sequence-axis coordinate.
|
| 45 |
fn present_offset(update_index: u32, cache_axis: u32) -> u32 {
|
| 46 |
var rem = update_index;
|
| 47 |
var offset = 0u;
|
| 48 |
+
{% for d in range(rank) %}
|
| 49 |
{% set u_stride = namespace(value=1) %}
|
| 50 |
+
{% for j in range(d + 1, rank) %}{% set u_stride.value = u_stride.value * updateShape[j] %}{% endfor %}
|
| 51 |
{% set p_stride = namespace(value=1) %}
|
| 52 |
+
{% for j in range(d + 1, rank) %}{% set p_stride.value = p_stride.value * pastShape[j] %}{% endfor %}
|
| 53 |
+
{% if d != axisSpec %}
|
| 54 |
let coord{{ d }} = rem / {{ max(u_stride.value, 1) }}u;
|
| 55 |
{% endif %}
|
| 56 |
rem = rem % {{ max(u_stride.value, 1) }}u;
|
| 57 |
+
{% if d == axisSpec %}
|
| 58 |
offset = offset + cache_axis * {{ p_stride.value }}u;
|
| 59 |
{% else %}
|
| 60 |
offset = offset + coord{{ d }} * {{ p_stride.value }}u;
|
|
|
|
| 64 |
}
|
| 65 |
|
| 66 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 67 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 68 |
{{ flat_index_2d("j") }}
|
| 69 |
// batch_idx is the leading (dim-0) coordinate; s is the coordinate along the
|
| 70 |
// sequence axis. write_indices is indexed per batch sample.
|
| 71 |
+
{% if hasIndices %}
|
| 72 |
let batch_idx = j / {{ max(dim0_stride.value, 1) }}u;
|
| 73 |
{% endif %}
|
| 74 |
let s = (j / {{ max(axis_stride.value, 1) }}u) % {{ max(seq_len, 1) }}u;
|
| 75 |
+
{% if hasIndices %}
|
| 76 |
let w = writeIndices[batch_idx];
|
| 77 |
{% else %}
|
| 78 |
let w = 0u;
|
| 79 |
{% endif %}
|
| 80 |
+
{% if circular %}
|
| 81 |
+
{% if seq_len > max_seq %}
|
| 82 |
+
// More updates than cache slots: positions wrap onto each other and the
|
| 83 |
+
// operator's sequential order makes the LAST update to a slot win. Only the
|
| 84 |
+
// final max_seq updates can be that last writer; earlier ones stay silent so
|
| 85 |
+
// the result does not depend on invocation scheduling.
|
| 86 |
+
if (s + {{ max_seq }}u < {{ seq_len }}u) {
|
| 87 |
+
return;
|
| 88 |
+
}
|
| 89 |
+
{% endif %}
|
| 90 |
+
// Reduce both terms before adding them: a write index near the u32 ceiling
|
| 91 |
+
// makes the sum wrap, and a wrapped sum lands on a different slot than the
|
| 92 |
+
// one the operator names unless the capacity happens to divide 2^32.
|
| 93 |
+
let cache_axis = (w % {{ max_seq }}u + s % {{ max_seq }}u) % {{ max_seq }}u;
|
| 94 |
{% else %}
|
| 95 |
let cache_axis = w + s;
|
| 96 |
{% endif %}
|
build/webgpu/test.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
{
|
| 2 |
-
"op": "ai.onnx.TensorScatter",
|
| 3 |
"fixtureArrays": {
|
| 4 |
"ort_linear_4d_spec_example_input_past": [1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 4, 3, 2, 1, 0],
|
| 5 |
"ort_linear_3d_default_axis_input_past": [1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 5, 4, 3, 2, 1],
|
|
@@ -45,7 +44,7 @@
|
|
| 45 |
{
|
| 46 |
"name": "decode_kv_update_f32_axis_minus2_compact",
|
| 47 |
"provenance": {
|
| 48 |
-
"notes": "
|
| 49 |
},
|
| 50 |
"attrs": { "mode": "linear", "axis": -2 },
|
| 51 |
"inputs": {
|
|
@@ -436,7 +435,7 @@
|
|
| 436 |
"name": "ort_standard_int16_linear_extremes",
|
| 437 |
"provenance": {
|
| 438 |
"source": "onnxruntime/core/providers/cpu/llm/tensorscatter.cc",
|
| 439 |
-
"notes": "ONNX TensorScatter-24 permits int16; both signed extrema
|
| 440 |
},
|
| 441 |
"attrs": { "axis": 2, "mode": "linear" },
|
| 442 |
"inputs": {
|
|
@@ -520,6 +519,54 @@
|
|
| 520 |
"writeIndices": { "dtype": "uint32", "shape": [1], "data": { "kind": "values", "values": [1] } }
|
| 521 |
},
|
| 522 |
"outputs": { "present": { "dtype": "bool", "shape": [1, 1, 4], "tolerance": 0 } }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
}
|
| 524 |
]
|
| 525 |
}
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"fixtureArrays": {
|
| 3 |
"ort_linear_4d_spec_example_input_past": [1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 4, 3, 2, 1, 0],
|
| 4 |
"ort_linear_3d_default_axis_input_past": [1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 5, 4, 3, 2, 1],
|
|
|
|
| 44 |
{
|
| 45 |
"name": "decode_kv_update_f32_axis_minus2_compact",
|
| 46 |
"provenance": {
|
| 47 |
+
"notes": "A compact decode-style TensorScatter uses axis=-2, per-batch write indices, a full-cache copy, and one-token update rows."
|
| 48 |
},
|
| 49 |
"attrs": { "mode": "linear", "axis": -2 },
|
| 50 |
"inputs": {
|
|
|
|
| 435 |
"name": "ort_standard_int16_linear_extremes",
|
| 436 |
"provenance": {
|
| 437 |
"source": "onnxruntime/core/providers/cpu/llm/tensorscatter.cc",
|
| 438 |
+
"notes": "ONNX TensorScatter-24 permits int16; both signed extrema verify logical int16 values stored in i32 slots during copy and overwrite."
|
| 439 |
},
|
| 440 |
"attrs": { "axis": 2, "mode": "linear" },
|
| 441 |
"inputs": {
|
|
|
|
| 519 |
"writeIndices": { "dtype": "uint32", "shape": [1], "data": { "kind": "values", "values": [1] } }
|
| 520 |
},
|
| 521 |
"outputs": { "present": { "dtype": "bool", "shape": [1, 1, 4], "tolerance": 0 } }
|
| 522 |
+
},
|
| 523 |
+
{
|
| 524 |
+
"name": "circular_update_longer_than_capacity_last_write_wins",
|
| 525 |
+
"provenance": {
|
| 526 |
+
"notes": "Six update rows enter a four-slot circular cache from write index 3: rows 0 and 4 both land on slot 3, while rows 1 and 5 land on slot 0. Sequence order makes the later row determine each collision, producing [[60,61],[30,31],[40,41],[50,51]] regardless of GPU scheduling."
|
| 527 |
+
},
|
| 528 |
+
"attrs": { "mode": "circular" },
|
| 529 |
+
"inputs": {
|
| 530 |
+
"past": { "dtype": "float32", "shape": [1, 4, 2], "data": { "kind": "constant", "value": 0.0 } },
|
| 531 |
+
"update": {
|
| 532 |
+
"dtype": "float32",
|
| 533 |
+
"shape": [1, 6, 2],
|
| 534 |
+
"data": {
|
| 535 |
+
"kind": "values",
|
| 536 |
+
"values": [10.0, 11.0, 20.0, 21.0, 30.0, 31.0, 40.0, 41.0, 50.0, 51.0, 60.0, 61.0]
|
| 537 |
+
}
|
| 538 |
+
},
|
| 539 |
+
"writeIndices": { "dtype": "uint32", "shape": [1], "data": { "kind": "values", "values": [3] } }
|
| 540 |
+
},
|
| 541 |
+
"outputs": { "present": { "dtype": "float32", "shape": [1, 4, 2], "tolerance": 0 } }
|
| 542 |
+
},
|
| 543 |
+
{
|
| 544 |
+
"name": "circular_write_index_at_u32_ceiling",
|
| 545 |
+
"provenance": {
|
| 546 |
+
"notes": "A circular cache reduces the write index modulo the capacity, and the sum of index and offset was formed before the reduction. At a write index near the u32 ceiling that sum wraps, so the second update lands on the slot the first one already took instead of the next one, and the two race. Slots 0 and 1 here are what int64 arithmetic names."
|
| 547 |
+
},
|
| 548 |
+
"attrs": { "mode": "circular" },
|
| 549 |
+
"inputs": {
|
| 550 |
+
"past": {
|
| 551 |
+
"dtype": "float32",
|
| 552 |
+
"shape": [1, 3, 2],
|
| 553 |
+
"data": { "kind": "values", "values": [-1.0, -2.0, -3.0, -4.0, -5.0, -6.0] }
|
| 554 |
+
},
|
| 555 |
+
"update": {
|
| 556 |
+
"dtype": "float32",
|
| 557 |
+
"shape": [1, 2, 2],
|
| 558 |
+
"data": { "kind": "values", "values": [10.0, 11.0, 20.0, 21.0] }
|
| 559 |
+
},
|
| 560 |
+
"writeIndices": { "dtype": "uint32", "shape": [1], "data": { "kind": "values", "values": [4294967295] } }
|
| 561 |
+
},
|
| 562 |
+
"outputs": {
|
| 563 |
+
"present": {
|
| 564 |
+
"dtype": "float32",
|
| 565 |
+
"shape": [1, 3, 2],
|
| 566 |
+
"data": { "kind": "values", "values": [10.0, 11.0, 20.0, 21.0, -5.0, -6.0] },
|
| 567 |
+
"tolerance": 0
|
| 568 |
+
}
|
| 569 |
+
}
|
| 570 |
}
|
| 571 |
]
|
| 572 |
}
|