sync 91d990483a17
Browse files- README.md +13 -10
- build/webgpu/bench.json +0 -1
- build/webgpu/manifest.json +114 -141
- build/webgpu/metadata.json +21 -10
- build/webgpu/resize-coord-transform.wgsl.jinja +63 -21
- build/webgpu/resize-generic.wgsl.jinja +21 -27
- build/webgpu/resize-linear-2x-stencil.wgsl.jinja +58 -19
- build/webgpu/resize-nearest-integer-scale.wgsl.jinja +8 -11
- build/webgpu/test.json +32 -1
README.md
CHANGED
|
@@ -18,16 +18,16 @@ See the [ONNX `Upsample` spec](https://onnx.ai/onnx/operators/onnx__Upsample.htm
|
|
| 18 |
|
| 19 |
## Inputs
|
| 20 |
|
| 21 |
-
| Name |
|
| 22 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
-
| `
|
| 24 |
-
| `scales` |
|
| 25 |
|
| 26 |
## Outputs
|
| 27 |
|
| 28 |
-
| Name |
|
| 29 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 30 |
-
| `
|
| 31 |
|
| 32 |
## Attributes
|
| 33 |
|
|
@@ -46,7 +46,7 @@ Default values (overridable per request):
|
|
| 46 |
|
| 47 |
## Files
|
| 48 |
|
| 49 |
-
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
|
| 50 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 51 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 52 |
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
|
@@ -57,15 +57,18 @@ Default values (overridable per request):
|
|
| 57 |
|
| 58 |
## Use with `@huggingface/kernels`
|
| 59 |
|
| 60 |
-
|
|
|
|
|
|
|
| 61 |
|
| 62 |
-
|
| 63 |
|
| 64 |
-
|
| 65 |
|
| 66 |
-
|
| 67 |
|
| 68 |
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
|
|
|
| 69 |
|
| 70 |
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 71 |
|
|
|
|
| 18 |
|
| 19 |
## Inputs
|
| 20 |
|
| 21 |
+
| Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
|
| 22 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `x` | `X` | `T` | — | — | Input tensor to upsample. | required |
|
| 24 |
+
| `scales` | — | `S` | `1` | — | Per-dimension scale factors, one value per input dimension. | required |
|
| 25 |
|
| 26 |
## Outputs
|
| 27 |
|
| 28 |
+
| Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
|
| 29 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 30 |
+
| `y` | `Y` | `T` | same as `x` | — | Upsampled output tensor; each dimension is `floor(input_dimension * scale)`. | required |
|
| 31 |
|
| 32 |
## Attributes
|
| 33 |
|
|
|
|
| 46 |
|
| 47 |
## Files
|
| 48 |
|
| 49 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
|
| 50 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 51 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 52 |
- [`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 |
+
Outputs with inferable metadata are allocated automatically. Explicit `outputs` entries request optional results or provide metadata that cannot be inferred from the supplied inputs and attributes.
|
| 65 |
|
| 66 |
+
This example supplies explicit metadata for:
|
| 67 |
|
| 68 |
+
- `y`
|
| 69 |
|
| 70 |
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 71 |
+
It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
|
| 72 |
|
| 73 |
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 74 |
|
build/webgpu/bench.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
{
|
| 2 |
-
"op": "ai.onnx.Upsample",
|
| 3 |
"tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
|
| 4 |
"cases": [
|
| 5 |
{
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
|
| 3 |
"cases": [
|
| 4 |
{
|
build/webgpu/manifest.json
CHANGED
|
@@ -2,222 +2,195 @@
|
|
| 2 |
"domain": "ai.onnx",
|
| 3 |
"name": "Upsample",
|
| 4 |
"sinceVersion": 9,
|
| 5 |
-
"
|
| 6 |
-
"
|
| 7 |
-
|
| 8 |
-
{
|
| 9 |
-
"role": "scales",
|
| 10 |
-
"dtype": "S",
|
| 11 |
-
"description": "Per-dimension scale factors, one value per input dimension.",
|
| 12 |
-
"rank": 1
|
| 13 |
-
}
|
| 14 |
-
],
|
| 15 |
-
"outputs": [
|
| 16 |
-
{
|
| 17 |
-
"role": "Y",
|
| 18 |
-
"dtype": "T",
|
| 19 |
-
"description": "Upsampled output tensor; each dimension is `floor(input_dimension * scale)`.",
|
| 20 |
-
"rank": "ranks.X"
|
| 21 |
-
}
|
| 22 |
-
],
|
| 23 |
-
"attributes": { "mode": "nearest" },
|
| 24 |
-
"attributeDescriptions": {
|
| 25 |
-
"mode": "Interpolation algorithm to use when mapping output coordinates back to input values; either `\"nearest\"` or `\"linear\"`."
|
| 26 |
-
},
|
| 27 |
"attributeConstraints": { "mode": { "values": ["nearest", "linear"] } },
|
| 28 |
"typeConstraints": { "T": ["float32", "float16", "int32", "int8", "uint8"], "S": ["float32"] },
|
| 29 |
-
"
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
"name": "
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
"buffer": { "type": "read-only-storage" },
|
| 47 |
-
"elementType": "f32"
|
| 48 |
-
},
|
| 49 |
-
{ "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" }
|
| 50 |
-
],
|
| 51 |
-
"nchw": [
|
| 52 |
-
{ "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
|
| 53 |
-
{
|
| 54 |
-
"name": "scales",
|
| 55 |
-
"arg": "scales",
|
| 56 |
-
"semantic": "scales",
|
| 57 |
-
"buffer": { "type": "read-only-storage" },
|
| 58 |
-
"elementType": "f32"
|
| 59 |
-
},
|
| 60 |
-
{ "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$outputElement" },
|
| 61 |
-
{
|
| 62 |
-
"name": "params",
|
| 63 |
-
"semantic": "kernel.params",
|
| 64 |
-
"buffer": { "type": "uniform" },
|
| 65 |
-
"struct": {
|
| 66 |
-
"name": "Params",
|
| 67 |
-
"fields": [
|
| 68 |
-
{ "name": "count", "type": "u32", "value": "numel(shapes.Y)" },
|
| 69 |
-
{ "name": "C", "type": "u32", "value": "dim(shapes.X, 1)" },
|
| 70 |
-
{ "name": "inH", "type": "u32", "value": "dim(shapes.X, 2)" },
|
| 71 |
-
{ "name": "inW", "type": "u32", "value": "dim(shapes.X, 3)" },
|
| 72 |
-
{ "name": "outH", "type": "u32", "value": "dim(shapes.Y, 2)" },
|
| 73 |
-
{ "name": "outW", "type": "u32", "value": "dim(shapes.Y, 3)" }
|
| 74 |
-
]
|
| 75 |
-
}
|
| 76 |
-
}
|
| 77 |
-
]
|
| 78 |
},
|
| 79 |
-
"constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
|
| 80 |
"variants": [
|
| 81 |
{
|
| 82 |
"id": "nearest_integer_scale_vec4",
|
| 83 |
"priority": 21,
|
| 84 |
-
"when": ["ranks.
|
| 85 |
-
"
|
| 86 |
"passes": [
|
| 87 |
{
|
| 88 |
"id": "main",
|
| 89 |
"name": "Upsample.nearestIntegerScaleVec4",
|
| 90 |
-
"
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
| 96 |
}
|
| 97 |
]
|
| 98 |
},
|
| 99 |
{
|
| 100 |
"id": "nearest_integer_scale",
|
| 101 |
"priority": 20,
|
| 102 |
-
"when": ["ranks.
|
| 103 |
-
"
|
| 104 |
"passes": [
|
| 105 |
{
|
| 106 |
"id": "main",
|
| 107 |
"name": "Upsample.nearestIntegerScale",
|
| 108 |
-
"
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
|
|
|
|
|
|
| 114 |
}
|
| 115 |
]
|
| 116 |
},
|
| 117 |
{
|
| 118 |
"id": "linear_2x_stencil_vec4",
|
| 119 |
"priority": 20,
|
| 120 |
-
"when": ["ranks.
|
| 121 |
-
"
|
| 122 |
"passes": [
|
| 123 |
{
|
| 124 |
"id": "main",
|
| 125 |
"name": "Upsample.linear2xStencilVec4",
|
| 126 |
-
"
|
| 127 |
-
|
| 128 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
},
|
| 130 |
-
"bindings": "
|
| 131 |
-
"dispatch": {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
}
|
| 133 |
]
|
| 134 |
},
|
| 135 |
{
|
| 136 |
"id": "linear_2x_stencil",
|
| 137 |
"priority": 19,
|
| 138 |
-
"when": ["ranks.
|
| 139 |
-
"
|
| 140 |
"passes": [
|
| 141 |
{
|
| 142 |
"id": "main",
|
| 143 |
"name": "Upsample.linear2xStencil",
|
| 144 |
-
"
|
| 145 |
-
|
| 146 |
-
"
|
|
|
|
|
|
|
|
|
|
| 147 |
},
|
| 148 |
-
"bindings": "
|
| 149 |
-
"dispatch": {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
}
|
| 151 |
]
|
| 152 |
},
|
| 153 |
{
|
| 154 |
"id": "nchw_rank4_vec4",
|
| 155 |
"priority": 1,
|
| 156 |
-
"when": ["ranks.
|
| 157 |
-
"
|
| 158 |
"passes": [
|
| 159 |
{
|
| 160 |
"id": "main",
|
| 161 |
"name": "Upsample.Vec4",
|
| 162 |
-
"
|
| 163 |
-
|
| 164 |
-
"
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
"scalesBuffer": true
|
| 171 |
-
}
|
| 172 |
},
|
| 173 |
-
"bindings": "
|
| 174 |
-
"dispatch": {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
}
|
| 176 |
]
|
| 177 |
},
|
| 178 |
{
|
| 179 |
"id": "nchw_rank4",
|
| 180 |
-
"when": ["ranks.
|
| 181 |
-
"
|
| 182 |
"passes": [
|
| 183 |
{
|
| 184 |
"id": "main",
|
| 185 |
"name": "Upsample",
|
| 186 |
-
"
|
| 187 |
-
|
| 188 |
-
"
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
"scalesBuffer": true
|
| 193 |
-
}
|
| 194 |
},
|
| 195 |
-
"bindings": "
|
| 196 |
-
"dispatch": {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
}
|
| 198 |
]
|
| 199 |
},
|
| 200 |
{
|
| 201 |
"id": "generic_rank_n",
|
| 202 |
"priority": 0,
|
| 203 |
-
"when": ["ranks.
|
| 204 |
"passes": [
|
| 205 |
{
|
| 206 |
"id": "main",
|
| 207 |
"name": "Upsample.Generic",
|
| 208 |
-
"
|
| 209 |
-
|
| 210 |
-
"
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
"scalesBuffer": true
|
| 217 |
-
}
|
| 218 |
},
|
| 219 |
-
"bindings": "
|
| 220 |
-
"dispatch": {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
}
|
| 222 |
]
|
| 223 |
}
|
|
|
|
| 2 |
"domain": "ai.onnx",
|
| 3 |
"name": "Upsample",
|
| 4 |
"sinceVersion": 9,
|
| 5 |
+
"inputs": { "x": { "onnx": "X", "dtype": "T" }, "scales": { "dtype": "S", "rank": 1 } },
|
| 6 |
+
"outputs": { "y": { "onnx": "Y", "dtype": "T", "rank": "ranks.x" } },
|
| 7 |
+
"attributes": { "mode": { "default": "nearest" } },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
"attributeConstraints": { "mode": { "values": ["nearest", "linear"] } },
|
| 9 |
"typeConstraints": { "T": ["float32", "float16", "int32", "int8", "uint8"], "S": ["float32"] },
|
| 10 |
+
"tunables": { "WORKGROUP_SIZE": { "default": 256 } },
|
| 11 |
+
"derive": { "scalar": "dtypes.T" },
|
| 12 |
+
"when": ["f16Ok(dtypes.T)"],
|
| 13 |
+
"bindings": {
|
| 14 |
+
"y": { "buffer": "storage", "elementType": "$outputElement" },
|
| 15 |
+
"scales": { "buffer": "read-only-storage", "elementType": "f32" },
|
| 16 |
+
"params": {
|
| 17 |
+
"buffer": "uniform",
|
| 18 |
+
"struct": [
|
| 19 |
+
{ "name": "count", "type": "u32", "value": "numel(shapes.y)" },
|
| 20 |
+
{ "name": "C", "type": "u32", "value": "dim(shapes.x, 1)" },
|
| 21 |
+
{ "name": "inH", "type": "u32", "value": "dim(shapes.x, 2)" },
|
| 22 |
+
{ "name": "inW", "type": "u32", "value": "dim(shapes.x, 3)" },
|
| 23 |
+
{ "name": "outH", "type": "u32", "value": "dim(shapes.y, 2)" },
|
| 24 |
+
{ "name": "outW", "type": "u32", "value": "dim(shapes.y, 3)" }
|
| 25 |
+
]
|
| 26 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
},
|
|
|
|
| 28 |
"variants": [
|
| 29 |
{
|
| 30 |
"id": "nearest_integer_scale_vec4",
|
| 31 |
"priority": 21,
|
| 32 |
+
"when": ["ranks.x == 4", "ranks.y == 4", "dim(shapes.x, 0) == dim(shapes.y, 0)", "dim(shapes.x, 1) == dim(shapes.y, 1)", "numel(shapes.y) > 0", "attrs.mode == \"nearest\"", "(dim(shapes.y, 2) == dim(shapes.x, 2) * 2 or dim(shapes.y, 2) == dim(shapes.x, 2) * 3 or dim(shapes.y, 2) == dim(shapes.x, 2) * 4)", "(dim(shapes.y, 3) == dim(shapes.x, 3) * 2 or dim(shapes.y, 3) == dim(shapes.x, 3) * 3 or dim(shapes.y, 3) == dim(shapes.x, 3) * 4)", "dim(shapes.y, 3) % 4 == 0"],
|
| 33 |
+
"derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"", "outputElement": "\"vec4<\" ~ dtypes.T ~ \">\"" },
|
| 34 |
"passes": [
|
| 35 |
{
|
| 36 |
"id": "main",
|
| 37 |
"name": "Upsample.nearestIntegerScaleVec4",
|
| 38 |
+
"shader": "resize-nearest-integer-scale.wgsl.jinja",
|
| 39 |
+
"derive": { "xShape": "shapes.x", "yShape": "shapes.y", "vec4": true },
|
| 40 |
+
"bindings": ["x", "y"],
|
| 41 |
+
"dispatch": {
|
| 42 |
+
"x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
|
| 43 |
+
"y": 1,
|
| 44 |
+
"z": 1
|
| 45 |
+
}
|
| 46 |
}
|
| 47 |
]
|
| 48 |
},
|
| 49 |
{
|
| 50 |
"id": "nearest_integer_scale",
|
| 51 |
"priority": 20,
|
| 52 |
+
"when": ["ranks.x == 4", "ranks.y == 4", "dim(shapes.x, 0) == dim(shapes.y, 0)", "dim(shapes.x, 1) == dim(shapes.y, 1)", "numel(shapes.y) > 0", "attrs.mode == \"nearest\"", "(dim(shapes.y, 2) == dim(shapes.x, 2) * 2 or dim(shapes.y, 2) == dim(shapes.x, 2) * 3 or dim(shapes.y, 2) == dim(shapes.x, 2) * 4)", "(dim(shapes.y, 3) == dim(shapes.x, 3) * 2 or dim(shapes.y, 3) == dim(shapes.x, 3) * 3 or dim(shapes.y, 3) == dim(shapes.x, 3) * 4)"],
|
| 53 |
+
"derive": { "outputElement": "dtypes.T" },
|
| 54 |
"passes": [
|
| 55 |
{
|
| 56 |
"id": "main",
|
| 57 |
"name": "Upsample.nearestIntegerScale",
|
| 58 |
+
"shader": "resize-nearest-integer-scale.wgsl.jinja",
|
| 59 |
+
"derive": { "xShape": "shapes.x", "yShape": "shapes.y", "vec4": false },
|
| 60 |
+
"bindings": ["x", "y"],
|
| 61 |
+
"dispatch": {
|
| 62 |
+
"x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
|
| 63 |
+
"y": 1,
|
| 64 |
+
"z": 1
|
| 65 |
+
}
|
| 66 |
}
|
| 67 |
]
|
| 68 |
},
|
| 69 |
{
|
| 70 |
"id": "linear_2x_stencil_vec4",
|
| 71 |
"priority": 20,
|
| 72 |
+
"when": ["ranks.x == 4", "ranks.y == 4", "dim(shapes.x, 0) == dim(shapes.y, 0)", "dim(shapes.x, 1) == dim(shapes.y, 1)", "numel(shapes.y) > 0", "attrs.mode == \"linear\"", "dim(shapes.y, 2) == dim(shapes.x, 2) * 2", "dim(shapes.y, 3) == dim(shapes.x, 3) * 2", "dim(shapes.y, 3) % 4 == 0"],
|
| 73 |
+
"derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"", "outputElement": "\"vec4<\" ~ dtypes.T ~ \">\"" },
|
| 74 |
"passes": [
|
| 75 |
{
|
| 76 |
"id": "main",
|
| 77 |
"name": "Upsample.linear2xStencilVec4",
|
| 78 |
+
"shader": "resize-linear-2x-stencil.wgsl.jinja",
|
| 79 |
+
"derive": {
|
| 80 |
+
"xShape": "shapes.x",
|
| 81 |
+
"yShape": "shapes.y",
|
| 82 |
+
"coordinateMode": "\"asymmetric\"",
|
| 83 |
+
"vec4": true,
|
| 84 |
+
"scalesBuffer": true
|
| 85 |
},
|
| 86 |
+
"bindings": ["x", "scales", "y"],
|
| 87 |
+
"dispatch": {
|
| 88 |
+
"x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
|
| 89 |
+
"y": 1,
|
| 90 |
+
"z": 1
|
| 91 |
+
}
|
| 92 |
}
|
| 93 |
]
|
| 94 |
},
|
| 95 |
{
|
| 96 |
"id": "linear_2x_stencil",
|
| 97 |
"priority": 19,
|
| 98 |
+
"when": ["ranks.x == 4", "ranks.y == 4", "dim(shapes.x, 0) == dim(shapes.y, 0)", "dim(shapes.x, 1) == dim(shapes.y, 1)", "numel(shapes.y) > 0", "attrs.mode == \"linear\"", "dim(shapes.y, 2) == dim(shapes.x, 2) * 2", "dim(shapes.y, 3) == dim(shapes.x, 3) * 2"],
|
| 99 |
+
"derive": { "outputElement": "dtypes.T" },
|
| 100 |
"passes": [
|
| 101 |
{
|
| 102 |
"id": "main",
|
| 103 |
"name": "Upsample.linear2xStencil",
|
| 104 |
+
"shader": "resize-linear-2x-stencil.wgsl.jinja",
|
| 105 |
+
"derive": {
|
| 106 |
+
"xShape": "shapes.x",
|
| 107 |
+
"yShape": "shapes.y",
|
| 108 |
+
"coordinateMode": "\"asymmetric\"",
|
| 109 |
+
"scalesBuffer": true
|
| 110 |
},
|
| 111 |
+
"bindings": ["x", "scales", "y"],
|
| 112 |
+
"dispatch": {
|
| 113 |
+
"x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
|
| 114 |
+
"y": 1,
|
| 115 |
+
"z": 1
|
| 116 |
+
}
|
| 117 |
}
|
| 118 |
]
|
| 119 |
},
|
| 120 |
{
|
| 121 |
"id": "nchw_rank4_vec4",
|
| 122 |
"priority": 1,
|
| 123 |
+
"when": ["ranks.x == 4", "ranks.y == 4", "dim(shapes.x, 0) == dim(shapes.y, 0)", "dim(shapes.x, 1) == dim(shapes.y, 1)", "dim(shapes.y, 3) % 4 == 0"],
|
| 124 |
+
"derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"", "outputElement": "\"vec4<\" ~ dtypes.T ~ \">\"" },
|
| 125 |
"passes": [
|
| 126 |
{
|
| 127 |
"id": "main",
|
| 128 |
"name": "Upsample.Vec4",
|
| 129 |
+
"shader": "resize-coord-transform.wgsl.jinja",
|
| 130 |
+
"derive": {
|
| 131 |
+
"modeSpec": "attrs.mode",
|
| 132 |
+
"coordinateMode": "\"asymmetric\"",
|
| 133 |
+
"nearestMode": "\"floor\"",
|
| 134 |
+
"vec4": true,
|
| 135 |
+
"vecType": "\"vec4<\" ~ dtypes.T ~ \">\"",
|
| 136 |
+
"scalesBuffer": true
|
|
|
|
|
|
|
| 137 |
},
|
| 138 |
+
"bindings": ["x", "scales", "y", "params"],
|
| 139 |
+
"dispatch": {
|
| 140 |
+
"x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 141 |
+
"y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 142 |
+
"z": 1
|
| 143 |
+
}
|
| 144 |
}
|
| 145 |
]
|
| 146 |
},
|
| 147 |
{
|
| 148 |
"id": "nchw_rank4",
|
| 149 |
+
"when": ["ranks.x == 4", "ranks.y == 4", "dim(shapes.x, 0) == dim(shapes.y, 0)", "dim(shapes.x, 1) == dim(shapes.y, 1)"],
|
| 150 |
+
"derive": { "outputElement": "dtypes.T" },
|
| 151 |
"passes": [
|
| 152 |
{
|
| 153 |
"id": "main",
|
| 154 |
"name": "Upsample",
|
| 155 |
+
"shader": "resize-coord-transform.wgsl.jinja",
|
| 156 |
+
"derive": {
|
| 157 |
+
"modeSpec": "attrs.mode",
|
| 158 |
+
"coordinateMode": "\"asymmetric\"",
|
| 159 |
+
"nearestMode": "\"floor\"",
|
| 160 |
+
"scalesBuffer": true
|
|
|
|
|
|
|
| 161 |
},
|
| 162 |
+
"bindings": ["x", "scales", "y", "params"],
|
| 163 |
+
"dispatch": {
|
| 164 |
+
"x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 165 |
+
"y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 166 |
+
"z": 1
|
| 167 |
+
}
|
| 168 |
}
|
| 169 |
]
|
| 170 |
},
|
| 171 |
{
|
| 172 |
"id": "generic_rank_n",
|
| 173 |
"priority": 0,
|
| 174 |
+
"when": ["ranks.x == ranks.y", "ranks.x >= 1", "numel(shapes.y) > 0", "(ranks.x != 4 or dim(shapes.x, 0) != dim(shapes.y, 0) or dim(shapes.x, 1) != dim(shapes.y, 1))"],
|
| 175 |
"passes": [
|
| 176 |
{
|
| 177 |
"id": "main",
|
| 178 |
"name": "Upsample.Generic",
|
| 179 |
+
"shader": "resize-generic.wgsl.jinja",
|
| 180 |
+
"derive": {
|
| 181 |
+
"xShape": "shapes.x",
|
| 182 |
+
"yShape": "shapes.y",
|
| 183 |
+
"modeSpec": "attrs.mode",
|
| 184 |
+
"coordinateMode": "\"asymmetric\"",
|
| 185 |
+
"nearestMode": "\"floor\"",
|
| 186 |
+
"scalesBuffer": true
|
|
|
|
|
|
|
| 187 |
},
|
| 188 |
+
"bindings": ["x", "scales", { "arg": "y" }],
|
| 189 |
+
"dispatch": {
|
| 190 |
+
"x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
|
| 191 |
+
"y": 1,
|
| 192 |
+
"z": 1
|
| 193 |
+
}
|
| 194 |
}
|
| 195 |
]
|
| 196 |
}
|
build/webgpu/metadata.json
CHANGED
|
@@ -1,21 +1,32 @@
|
|
| 1 |
{
|
| 2 |
"name": "ai.onnx.Upsample",
|
| 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 |
-
"resize-coord-transform.wgsl.jinja": "+
|
| 13 |
-
"resize-generic.wgsl.jinja": "
|
| 14 |
-
"resize-linear-2x-stencil.wgsl.jinja": "
|
| 15 |
-
"resize-nearest-integer-scale.wgsl.jinja": "
|
| 16 |
-
"test.json": "
|
| 17 |
}
|
| 18 |
},
|
| 19 |
-
"provenance": { "kernel": { "sha": "
|
| 20 |
-
"webgpu": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "ai.onnx.Upsample",
|
| 3 |
+
"id": "_ai_onnx_upsample_webgpu_226c6a3",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
+
"bench.json": "F6Vym+nah84vMiKJZvh+qKarMEzp84955NrGH18TWBs=",
|
| 11 |
+
"manifest.json": "ZZF7ZLeywO+hHftIgE5IeP4cufl+b3GKDYU9AV/inS4=",
|
| 12 |
+
"resize-coord-transform.wgsl.jinja": "WzhlhFxNz877wX29wcns2y+IJTC5g/IzYX5NEH27n6s=",
|
| 13 |
+
"resize-generic.wgsl.jinja": "bAUivK8i1ccgtOFXGB7Y9kTjyRfql5TUb+sikbvOMPk=",
|
| 14 |
+
"resize-linear-2x-stencil.wgsl.jinja": "r98ldVQUx6+bQnoqw9Fjt4JtygJqj5yEKXAv2DOJ5ic=",
|
| 15 |
+
"resize-nearest-integer-scale.wgsl.jinja": "GnmGPu4MyJrir+dzp3ucQ/ADVW9mJDcU1czn6L/RPF8=",
|
| 16 |
+
"test.json": "Cmo87nuUyMyjpettO/Nks1mu7Z00DCEnYyaHf5fQNsI="
|
| 17 |
}
|
| 18 |
},
|
| 19 |
+
"provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
|
| 20 |
+
"webgpu": {
|
| 21 |
+
"manifestSpec": "2.0",
|
| 22 |
+
"variants": {
|
| 23 |
+
"nearest_integer_scale_vec4": ["resize-nearest-integer-scale.wgsl.jinja"],
|
| 24 |
+
"nearest_integer_scale": ["resize-nearest-integer-scale.wgsl.jinja"],
|
| 25 |
+
"linear_2x_stencil_vec4": ["resize-linear-2x-stencil.wgsl.jinja"],
|
| 26 |
+
"linear_2x_stencil": ["resize-linear-2x-stencil.wgsl.jinja"],
|
| 27 |
+
"nchw_rank4_vec4": ["resize-coord-transform.wgsl.jinja"],
|
| 28 |
+
"nchw_rank4": ["resize-coord-transform.wgsl.jinja"],
|
| 29 |
+
"generic_rank_n": ["resize-generic.wgsl.jinja"]
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
}
|
build/webgpu/resize-coord-transform.wgsl.jinja
CHANGED
|
@@ -3,9 +3,8 @@
|
|
| 3 |
// Nearest-index rounding is load-bearing for ONNX compatibility:
|
| 4 |
// round_prefer_ceil ties toward positive infinity, while round_prefer_floor
|
| 5 |
// uses its specified epsilon.
|
| 6 |
-
{%
|
| 7 |
-
|
| 8 |
-
{% endif %}
|
| 9 |
{{ env.wgsl.resourceDeclarations }}
|
| 10 |
{% set wg = workgroupSize if workgroupSize is defined else tunables.WORKGROUP_SIZE %}
|
| 11 |
|
|
@@ -19,7 +18,7 @@ fn source_coord(out_coord: u32, in_size: u32, out_size: u32, explicit_scale: f32
|
|
| 19 |
/* Backend-stable nearest-neighbor coordinate selection. Inferred align-corners,
|
| 20 |
* asymmetric, and half-pixel transforms use exact integer ratios. This matters
|
| 21 |
* at nearest-mode ties: reassociating an f32 division into a reciprocal
|
| 22 |
-
* multiply can move an exact 0.5 to either side of the boundary. */{% if
|
| 23 |
fn nearest_index(v: f32) -> i32 {
|
| 24 |
return i32(floor(v));
|
| 25 |
}
|
|
@@ -67,7 +66,7 @@ fn input_at(base: u32, h: i32, w: i32) -> f32 {
|
|
| 67 |
return f32(x[base + hh * params.inW + ww]);
|
| 68 |
}
|
| 69 |
|
| 70 |
-
{% if
|
| 71 |
// Clamp before choosing neighbors. At an edge both neighbors coincide and
|
| 72 |
// receive half weight; the four-term order is observable for integer outputs.
|
| 73 |
fn bilinear_sample(base: u32, source_y: f32, source_x: f32) -> f32 {
|
|
@@ -92,20 +91,24 @@ fn bilinear_sample(base: u32, source_y: f32, source_x: f32) -> f32 {
|
|
| 92 |
{% endif %}
|
| 93 |
|
| 94 |
@compute @workgroup_size({{ wg }})
|
| 95 |
-
fn main(@builtin(global_invocation_id) gid: vec3<u32>
|
| 96 |
// 2D-folded flat index: gid.y carries the high bits past the
|
| 97 |
-
//
|
| 98 |
-
let flat = gid.x + gid.y *
|
| 99 |
-
|
| 100 |
-
//
|
| 101 |
-
//
|
| 102 |
-
//
|
| 103 |
let scaleH_val = f32(scales[2]);
|
| 104 |
let scaleW_val = f32(scales[3]);
|
| 105 |
-
{%
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
let count4 = params.count / 4u;
|
| 110 |
if (flat >= count4) { return; }
|
| 111 |
let i = flat * 4u;
|
|
@@ -115,14 +118,49 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
|
|
| 115 |
let c = (row / params.outH) % params.C;
|
| 116 |
let n = row / (params.outH * params.C);
|
| 117 |
let base = (n * params.C + c) * params.inH * params.inW;
|
| 118 |
-
{% if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
let sy = source_coord(oh, params.inH, params.outH, scaleH_val);
|
| 120 |
let sx0 = source_coord(ow0, params.inW, params.outW, scaleW_val);
|
| 121 |
let sx1 = source_coord(ow0 + 1u, params.inW, params.outW, scaleW_val);
|
| 122 |
let sx2 = source_coord(ow0 + 2u, params.inW, params.outW, scaleW_val);
|
| 123 |
let sx3 = source_coord(ow0 + 3u, params.inW, params.outW, scaleW_val);
|
| 124 |
{% endif %}
|
| 125 |
-
{% if
|
| 126 |
let ih = nearest_coord_index(oh, params.inH, params.outH, scaleH_val);
|
| 127 |
let r = vec4<f32>(
|
| 128 |
input_at(base, ih, nearest_coord_index(ow0, params.inW, params.outW, scaleW_val)),
|
|
@@ -136,7 +174,11 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
|
|
| 136 |
bilinear_sample(base, sy, sx2),
|
| 137 |
bilinear_sample(base, sy, sx3));
|
| 138 |
{% endif %}
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
{% else %}
|
| 141 |
let i = flat;
|
| 142 |
if (i >= params.count) { return; }
|
|
@@ -145,11 +187,11 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
|
|
| 145 |
let c = (i / (params.outW * params.outH)) % params.C;
|
| 146 |
let n = i / (params.outW * params.outH * params.C);
|
| 147 |
let base = (n * params.C + c) * params.inH * params.inW;
|
| 148 |
-
{% if
|
| 149 |
let sy = source_coord(oh, params.inH, params.outH, scaleH_val);
|
| 150 |
let sx = source_coord(ow, params.inW, params.outW, scaleW_val);
|
| 151 |
{% endif %}
|
| 152 |
-
{% if
|
| 153 |
let ih = nearest_coord_index(oh, params.inH, params.outH, scaleH_val);
|
| 154 |
let iw = nearest_coord_index(ow, params.inW, params.outW, scaleW_val);
|
| 155 |
let result = input_at(base, ih, iw);
|
|
|
|
| 3 |
// Nearest-index rounding is load-bearing for ONNX compatibility:
|
| 4 |
// round_prefer_ceil ties toward positive infinity, while round_prefer_floor
|
| 5 |
// uses its specified epsilon.
|
| 6 |
+
{% set scalesBuffer = scalesBuffer is defined and scalesBuffer %}
|
| 7 |
+
{% set scalarX4 = scalarX4 is defined and scalarX4 %}
|
|
|
|
| 8 |
{{ env.wgsl.resourceDeclarations }}
|
| 9 |
{% set wg = workgroupSize if workgroupSize is defined else tunables.WORKGROUP_SIZE %}
|
| 10 |
|
|
|
|
| 18 |
/* Backend-stable nearest-neighbor coordinate selection. Inferred align-corners,
|
| 19 |
* asymmetric, and half-pixel transforms use exact integer ratios. This matters
|
| 20 |
* at nearest-mode ties: reassociating an f32 division into a reciprocal
|
| 21 |
+
* multiply can move an exact 0.5 to either side of the boundary. */{% if modeSpec == "nearest" %}
|
| 22 |
fn nearest_index(v: f32) -> i32 {
|
| 23 |
return i32(floor(v));
|
| 24 |
}
|
|
|
|
| 66 |
return f32(x[base + hh * params.inW + ww]);
|
| 67 |
}
|
| 68 |
|
| 69 |
+
{% if modeSpec == "linear" %}
|
| 70 |
// Clamp before choosing neighbors. At an edge both neighbors coincide and
|
| 71 |
// receive half weight; the four-term order is observable for integer outputs.
|
| 72 |
fn bilinear_sample(base: u32, source_y: f32, source_x: f32) -> f32 {
|
|
|
|
| 91 |
{% endif %}
|
| 92 |
|
| 93 |
@compute @workgroup_size({{ wg }})
|
| 94 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 95 |
// 2D-folded flat index: gid.y carries the high bits past the
|
| 96 |
+
// per-axis dispatch fold width.
|
| 97 |
+
let flat = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wg }}u;
|
| 98 |
+
{% if scalesBuffer %}
|
| 99 |
+
// Read spatial scales from NCHW axes 2 and 3. Inferring a scale from the
|
| 100 |
+
// rounded output extent would discard its fractional part; for example,
|
| 101 |
+
// input 2 scaled by 2.333 has output extent 4, whose extent ratio is 2.0.
|
| 102 |
let scaleH_val = f32(scales[2]);
|
| 103 |
let scaleW_val = f32(scales[3]);
|
| 104 |
+
{% else %}
|
| 105 |
+
let scaleH_val = params.scaleH;
|
| 106 |
+
let scaleW_val = params.scaleW;
|
| 107 |
+
{% endif %}
|
| 108 |
+
{% if vec4 %}
|
| 109 |
+
// One invocation owns four contiguous output columns. Because outW is
|
| 110 |
+
// divisible by four, the group never crosses a row; all lanes share n, c,
|
| 111 |
+
// oh, and source row coordinate sy, while sx differs per lane.
|
| 112 |
let count4 = params.count / 4u;
|
| 113 |
if (flat >= count4) { return; }
|
| 114 |
let i = flat * 4u;
|
|
|
|
| 118 |
let c = (row / params.outH) % params.C;
|
| 119 |
let n = row / (params.outH * params.C);
|
| 120 |
let base = (n * params.C + c) * params.inH * params.inW;
|
| 121 |
+
{% if modeSpec != "nearest" %}
|
| 122 |
+
let sy = source_coord(oh, params.inH, params.outH, scaleH_val);
|
| 123 |
+
let sx0 = source_coord(ow0, params.inW, params.outW, scaleW_val);
|
| 124 |
+
let sx1 = source_coord(ow0 + 1u, params.inW, params.outW, scaleW_val);
|
| 125 |
+
let sx2 = source_coord(ow0 + 2u, params.inW, params.outW, scaleW_val);
|
| 126 |
+
let sx3 = source_coord(ow0 + 3u, params.inW, params.outW, scaleW_val);
|
| 127 |
+
{% endif %}
|
| 128 |
+
{% if modeSpec == "nearest" %}
|
| 129 |
+
let ih = nearest_coord_index(oh, params.inH, params.outH, scaleH_val);
|
| 130 |
+
let r = vec4<f32>(
|
| 131 |
+
input_at(base, ih, nearest_coord_index(ow0, params.inW, params.outW, scaleW_val)),
|
| 132 |
+
input_at(base, ih, nearest_coord_index(ow0 + 1u, params.inW, params.outW, scaleW_val)),
|
| 133 |
+
input_at(base, ih, nearest_coord_index(ow0 + 2u, params.inW, params.outW, scaleW_val)),
|
| 134 |
+
input_at(base, ih, nearest_coord_index(ow0 + 3u, params.inW, params.outW, scaleW_val)));
|
| 135 |
+
{% else %}
|
| 136 |
+
let r = vec4<f32>(
|
| 137 |
+
bilinear_sample(base, sy, sx0),
|
| 138 |
+
bilinear_sample(base, sy, sx1),
|
| 139 |
+
bilinear_sample(base, sy, sx2),
|
| 140 |
+
bilinear_sample(base, sy, sx3));
|
| 141 |
+
{% endif %}
|
| 142 |
+
y[flat] = {{ vecType }}(r);
|
| 143 |
+
{% elif scalarX4 %}
|
| 144 |
+
// Each invocation computes four row-local columns without a vec4 storage
|
| 145 |
+
// binding. The final one to three columns use guarded scalar stores.
|
| 146 |
+
let groups_per_row = (params.outW + 3u) / 4u;
|
| 147 |
+
let group_count = (params.count / params.outW) * groups_per_row;
|
| 148 |
+
if (flat >= group_count) { return; }
|
| 149 |
+
let group_in_row = flat % groups_per_row;
|
| 150 |
+
let row = flat / groups_per_row;
|
| 151 |
+
let ow0 = group_in_row * 4u;
|
| 152 |
+
let oh = row % params.outH;
|
| 153 |
+
let c = (row / params.outH) % params.C;
|
| 154 |
+
let n = row / (params.outH * params.C);
|
| 155 |
+
let base = (n * params.C + c) * params.inH * params.inW;
|
| 156 |
+
{% if modeSpec != "nearest" %}
|
| 157 |
let sy = source_coord(oh, params.inH, params.outH, scaleH_val);
|
| 158 |
let sx0 = source_coord(ow0, params.inW, params.outW, scaleW_val);
|
| 159 |
let sx1 = source_coord(ow0 + 1u, params.inW, params.outW, scaleW_val);
|
| 160 |
let sx2 = source_coord(ow0 + 2u, params.inW, params.outW, scaleW_val);
|
| 161 |
let sx3 = source_coord(ow0 + 3u, params.inW, params.outW, scaleW_val);
|
| 162 |
{% endif %}
|
| 163 |
+
{% if modeSpec == "nearest" %}
|
| 164 |
let ih = nearest_coord_index(oh, params.inH, params.outH, scaleH_val);
|
| 165 |
let r = vec4<f32>(
|
| 166 |
input_at(base, ih, nearest_coord_index(ow0, params.inW, params.outW, scaleW_val)),
|
|
|
|
| 174 |
bilinear_sample(base, sy, sx2),
|
| 175 |
bilinear_sample(base, sy, sx3));
|
| 176 |
{% endif %}
|
| 177 |
+
let i = row * params.outW + ow0;
|
| 178 |
+
y[i] = {{ scalar }}(r.x);
|
| 179 |
+
if (ow0 + 1u < params.outW) { y[i + 1u] = {{ scalar }}(r.y); }
|
| 180 |
+
if (ow0 + 2u < params.outW) { y[i + 2u] = {{ scalar }}(r.z); }
|
| 181 |
+
if (ow0 + 3u < params.outW) { y[i + 3u] = {{ scalar }}(r.w); }
|
| 182 |
{% else %}
|
| 183 |
let i = flat;
|
| 184 |
if (i >= params.count) { return; }
|
|
|
|
| 187 |
let c = (i / (params.outW * params.outH)) % params.C;
|
| 188 |
let n = i / (params.outW * params.outH * params.C);
|
| 189 |
let base = (n * params.C + c) * params.inH * params.inW;
|
| 190 |
+
{% if modeSpec != "nearest" %}
|
| 191 |
let sy = source_coord(oh, params.inH, params.outH, scaleH_val);
|
| 192 |
let sx = source_coord(ow, params.inW, params.outW, scaleW_val);
|
| 193 |
{% endif %}
|
| 194 |
+
{% if modeSpec == "nearest" %}
|
| 195 |
let ih = nearest_coord_index(oh, params.inH, params.outH, scaleH_val);
|
| 196 |
let iw = nearest_coord_index(ow, params.inW, params.outW, scaleW_val);
|
| 197 |
let result = input_at(base, ih, iw);
|
build/webgpu/resize-generic.wgsl.jinja
CHANGED
|
@@ -1,18 +1,13 @@
|
|
| 1 |
-
|
| 2 |
-
//
|
| 3 |
-
//
|
| 4 |
-
//
|
| 5 |
-
// consistent. Linear interpolation runs only over resized axes; passthrough
|
| 6 |
-
// axes contribute one tap.
|
| 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 |
-
{%- set xs =
|
| 13 |
-
{% set ys =
|
| 14 |
{% set r = xs | length -%}
|
| 15 |
-
{% set usesExplicitScale =
|
| 16 |
|
| 17 |
{% set resolvedScales = namespace(values = []) %}
|
| 18 |
{% for a in range(r) %}
|
|
@@ -36,10 +31,9 @@ enable f16;
|
|
| 36 |
|
| 37 |
const WG: u32 = {{ wg }}u;
|
| 38 |
|
| 39 |
-
// Runtime form
|
| 40 |
-
//
|
| 41 |
-
|
| 42 |
-
{% if source.mode == "nearest" and k > 0 and (source.roi is not defined or (source.roi | length) == 0) and source.coordinateMode != "align_corners" %}
|
| 43 |
|
| 44 |
fn source_coord(out_coord: u32, in_size: u32, out_size: u32, explicit_scale: f32) -> f32 {
|
| 45 |
return select(f32(out_coord) * f32(in_size) / f32(out_size), f32(out_coord) / explicit_scale, explicit_scale > 0.0);
|
|
@@ -57,10 +51,10 @@ fn source_coord(out_coord: u32, in_size: u32, out_size: u32, explicit_scale: f32
|
|
| 57 |
(f32({{ oc }}) / scales[{{ a }}])
|
| 58 |
{% endmacro %}
|
| 59 |
|
| 60 |
-
{% if
|
| 61 |
// Keys cubic convolution kernel with coefficient coeffA.
|
| 62 |
fn cubic_weight(s_in: f32) -> f32 {
|
| 63 |
-
let a = f32({{
|
| 64 |
let s = abs(s_in);
|
| 65 |
let s2 = s * s;
|
| 66 |
let s3 = s2 * s;
|
|
@@ -70,15 +64,15 @@ fn cubic_weight(s_in: f32) -> f32 {
|
|
| 70 |
}
|
| 71 |
{% endif %}
|
| 72 |
|
| 73 |
-
{% if
|
| 74 |
/* Backend-stable nearest-neighbor coordinate selection. Inferred align-corners,
|
| 75 |
* asymmetric, and half-pixel transforms use exact integer ratios. This matters
|
| 76 |
* at nearest-mode ties: reassociating an f32 division into a reciprocal
|
| 77 |
-
* multiply can move an exact 0.5 to either side of the boundary. */{% set ratioMode =
|
| 78 |
-
{% set inferredHalfPixelMode =
|
| 79 |
-
{% set noRoi =
|
| 80 |
-
{% set usesExplicitScale =
|
| 81 |
-
{% if
|
| 82 |
fn nearest_index(v: f32) -> i32 {
|
| 83 |
return i32(floor(v));
|
| 84 |
}
|
|
@@ -177,11 +171,11 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
|
|
| 177 |
{% endfor %}
|
| 178 |
// Per-axis source coordinates.
|
| 179 |
{% for a in range(r) %}
|
| 180 |
-
{% if a in resized.list and (
|
| 181 |
let s{{ a }} = {{ src_coord(a, out_coord(a)) }};
|
| 182 |
{% endif %}
|
| 183 |
{% endfor %}
|
| 184 |
-
{% if
|
| 185 |
{% for a in range(r) %}
|
| 186 |
{% if a in resized.list %}
|
| 187 |
{% set explicitScale = "f32(scales[" ~ a ~ "])" %}
|
|
@@ -191,7 +185,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
|
|
| 191 |
{% endif %}
|
| 192 |
{% endfor %}
|
| 193 |
y[i] = {{ scalar }}(f32(x[0u{% for a in range(r) %} + idx{{ a }} * {{ istride(a) }}u{% endfor %}]));
|
| 194 |
-
{% elif
|
| 195 |
// Separable Keys cubic: 4 taps per resized axis (offsets -1..2), passthrough
|
| 196 |
// axes contribute a single tap. 4^k taps total.
|
| 197 |
{% for a in resized.list %}
|
|
|
|
| 1 |
+
{% set coeffA = coeffA | default(0) %}
|
| 2 |
+
// Rank-generic interpolation for arbitrary ranks and resized axes. Scales are
|
| 3 |
+
// read from the input buffer, and linear interpolation runs only over resized
|
| 4 |
+
// axes; passthrough axes contribute one tap.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
{{ env.wgsl.resourceDeclarations }}
|
| 6 |
{% set wg = workgroupSize if workgroupSize is defined else tunables.WORKGROUP_SIZE %}
|
| 7 |
+
{%- set xs = xShape %}
|
| 8 |
+
{% set ys = yShape %}
|
| 9 |
{% set r = xs | length -%}
|
| 10 |
+
{% set usesExplicitScale = coordinateMode not in ["align_corners", "tf_crop_and_resize"] %}
|
| 11 |
|
| 12 |
{% set resolvedScales = namespace(values = []) %}
|
| 13 |
{% for a in range(r) %}
|
|
|
|
| 31 |
|
| 32 |
const WG: u32 = {{ wg }}u;
|
| 33 |
|
| 34 |
+
// Runtime coordinate form for nearest interpolation when an exact
|
| 35 |
+
// inferred-ratio expression is unavailable.
|
| 36 |
+
{% if modeSpec == "nearest" and k > 0 and (roi is not defined or (roi | length) == 0) and coordinateMode != "align_corners" %}
|
|
|
|
| 37 |
|
| 38 |
fn source_coord(out_coord: u32, in_size: u32, out_size: u32, explicit_scale: f32) -> f32 {
|
| 39 |
return select(f32(out_coord) * f32(in_size) / f32(out_size), f32(out_coord) / explicit_scale, explicit_scale > 0.0);
|
|
|
|
| 51 |
(f32({{ oc }}) / scales[{{ a }}])
|
| 52 |
{% endmacro %}
|
| 53 |
|
| 54 |
+
{% if modeSpec == "cubic" %}
|
| 55 |
// Keys cubic convolution kernel with coefficient coeffA.
|
| 56 |
fn cubic_weight(s_in: f32) -> f32 {
|
| 57 |
+
let a = f32({{ coeffA }});
|
| 58 |
let s = abs(s_in);
|
| 59 |
let s2 = s * s;
|
| 60 |
let s3 = s2 * s;
|
|
|
|
| 64 |
}
|
| 65 |
{% endif %}
|
| 66 |
|
| 67 |
+
{% if modeSpec == "nearest" and k > 0 %}
|
| 68 |
/* Backend-stable nearest-neighbor coordinate selection. Inferred align-corners,
|
| 69 |
* asymmetric, and half-pixel transforms use exact integer ratios. This matters
|
| 70 |
* at nearest-mode ties: reassociating an f32 division into a reciprocal
|
| 71 |
+
* multiply can move an exact 0.5 to either side of the boundary. */{% set ratioMode = coordinateMode in ["align_corners", "asymmetric"] %}
|
| 72 |
+
{% set inferredHalfPixelMode = coordinateMode in ["pytorch_half_pixel", "half_pixel", "half_pixel_symmetric"] %}
|
| 73 |
+
{% set noRoi = roi is not defined or (roi | length) == 0 %}
|
| 74 |
+
{% set usesExplicitScale = coordinateMode not in ["align_corners", "tf_crop_and_resize"] %}
|
| 75 |
+
{% if modeSpec == "nearest" %}
|
| 76 |
fn nearest_index(v: f32) -> i32 {
|
| 77 |
return i32(floor(v));
|
| 78 |
}
|
|
|
|
| 171 |
{% endfor %}
|
| 172 |
// Per-axis source coordinates.
|
| 173 |
{% for a in range(r) %}
|
| 174 |
+
{% if a in resized.list and (modeSpec != "nearest" or (roi is defined and roi)) %}
|
| 175 |
let s{{ a }} = {{ src_coord(a, out_coord(a)) }};
|
| 176 |
{% endif %}
|
| 177 |
{% endfor %}
|
| 178 |
+
{% if modeSpec == "nearest" %}
|
| 179 |
{% for a in range(r) %}
|
| 180 |
{% if a in resized.list %}
|
| 181 |
{% set explicitScale = "f32(scales[" ~ a ~ "])" %}
|
|
|
|
| 185 |
{% endif %}
|
| 186 |
{% endfor %}
|
| 187 |
y[i] = {{ scalar }}(f32(x[0u{% for a in range(r) %} + idx{{ a }} * {{ istride(a) }}u{% endfor %}]));
|
| 188 |
+
{% elif modeSpec == "cubic" %}
|
| 189 |
// Separable Keys cubic: 4 taps per resized axis (offsets -1..2), passthrough
|
| 190 |
// axes contribute a single tap. 4^k taps total.
|
| 191 |
{% for a in resized.list %}
|
build/webgpu/resize-linear-2x-stencil.wgsl.jinja
CHANGED
|
@@ -1,35 +1,55 @@
|
|
| 1 |
-
|
|
|
|
| 2 |
// interpolation weights collapse to fixed per-parity stencils, replacing
|
| 3 |
// per-element floating-point coordinate divisions with integer parity tests
|
| 4 |
// and constants.
|
| 5 |
//
|
| 6 |
-
//
|
| 7 |
-
//
|
| 8 |
-
// 0.25. For asymmetric mode, src = o / 2: even outputs use fraction 0.0 and odd
|
| 9 |
-
// outputs use 0.5. align_corners remains on the generic fallback because its
|
| 10 |
-
// position-dependent scale does not reduce to fixed stencils.
|
| 11 |
//
|
| 12 |
// Input coordinates clamp at the edges, including the q - 1 and q + 1 border
|
| 13 |
-
// taps.
|
| 14 |
-
{% if usesF16 %}
|
| 15 |
-
enable f16;
|
| 16 |
-
{% endif %}
|
| 17 |
{{ env.wgsl.resourceDeclarations }}
|
| 18 |
{% set wg = workgroupSize if workgroupSize is defined else tunables.WORKGROUP_SIZE %}
|
| 19 |
|
| 20 |
const WG: u32 = {{ wg }}u;
|
| 21 |
-
const IN_H: u32 = {{
|
| 22 |
-
const IN_W: u32 = {{
|
| 23 |
-
const IN_HW: u32 = {{
|
| 24 |
-
const OUT_H: u32 = {{
|
| 25 |
-
const OUT_W: u32 = {{
|
| 26 |
-
const COUNT: u32 = {{
|
| 27 |
|
| 28 |
fn input_at(base: u32, h: i32, w: i32) -> f32 {
|
| 29 |
let hh = u32(clamp(h, 0, i32(IN_H) - 1));
|
| 30 |
let ww = u32(clamp(w, 0, i32(IN_W) - 1));
|
| 31 |
return f32(x[base + hh * IN_W + ww]);
|
| 32 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
@compute @workgroup_size({{ wg }})
|
| 35 |
fn main(
|
|
@@ -37,13 +57,16 @@ fn main(
|
|
| 37 |
@builtin(num_workgroups) nwg: vec3<u32>
|
| 38 |
) {
|
| 39 |
let stride = nwg.x * WG;
|
| 40 |
-
{% if
|
|
|
|
|
|
|
|
|
|
| 41 |
// Vec4 path: one thread owns 4 contiguous output columns (OUT_W % 4 == 0, so
|
| 42 |
// a 4-group never crosses a row). All 4 lanes share the same oh -> same source
|
| 43 |
// rows (y0,y1) and the same 4-column source window (qx0-1 .. qx0+2); load that
|
| 44 |
// window once, compute the 4 bilinear results with the baked per-lane parity
|
| 45 |
-
// weights, and emit a single
|
| 46 |
-
//
|
| 47 |
let OUT_W4 = OUT_W / 4u;
|
| 48 |
let COUNT4 = COUNT / 4u;
|
| 49 |
for (var v = gid.x; v < COUNT4; v += stride) {
|
|
@@ -53,6 +76,16 @@ fn main(
|
|
| 53 |
let nc = tt / OUT_H;
|
| 54 |
let base = nc * IN_HW;
|
| 55 |
let ow0 = ow4 * 4u;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
let qx0 = i32(ow0 / 2u);
|
| 57 |
let y0 = i32(oh / 2u);
|
| 58 |
let wy = select(0.0, 0.5, (oh & 1u) == 1u);
|
|
@@ -77,6 +110,12 @@ fn main(
|
|
| 77 |
let oh = t % OUT_H;
|
| 78 |
let nc = t / OUT_H;
|
| 79 |
let base = nc * IN_HW;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
let qy = i32(oh / 2u);
|
| 81 |
let qx = i32(ow / 2u);
|
| 82 |
let oddY = (oh & 1u) == 1u;
|
|
|
|
| 1 |
+
{% set scalesBuffer = scalesBuffer if scalesBuffer is defined else false %}
|
| 2 |
+
// NCHW bilinear 2x upscale on both spatial axes. The
|
| 3 |
// interpolation weights collapse to fixed per-parity stencils, replacing
|
| 4 |
// per-element floating-point coordinate divisions with integer parity tests
|
| 5 |
// and constants.
|
| 6 |
//
|
| 7 |
+
// With asymmetric coordinates, src = o / 2: even outputs use fraction 0.0 and
|
| 8 |
+
// odd outputs use fraction 0.5.
|
|
|
|
|
|
|
|
|
|
| 9 |
//
|
| 10 |
// Input coordinates clamp at the edges, including the q - 1 and q + 1 border
|
| 11 |
+
// taps. Interpolation weights are constructed in f32 before the output cast.
|
|
|
|
|
|
|
|
|
|
| 12 |
{{ env.wgsl.resourceDeclarations }}
|
| 13 |
{% set wg = workgroupSize if workgroupSize is defined else tunables.WORKGROUP_SIZE %}
|
| 14 |
|
| 15 |
const WG: u32 = {{ wg }}u;
|
| 16 |
+
const IN_H: u32 = {{ xShape[2] }}u;
|
| 17 |
+
const IN_W: u32 = {{ xShape[3] }}u;
|
| 18 |
+
const IN_HW: u32 = {{ xShape[2] * xShape[3] }}u;
|
| 19 |
+
const OUT_H: u32 = {{ yShape[2] }}u;
|
| 20 |
+
const OUT_W: u32 = {{ yShape[3] }}u;
|
| 21 |
+
const COUNT: u32 = {{ yShape[0] * yShape[1] * yShape[2] * yShape[3] }}u;
|
| 22 |
|
| 23 |
fn input_at(base: u32, h: i32, w: i32) -> f32 {
|
| 24 |
let hh = u32(clamp(h, 0, i32(IN_H) - 1));
|
| 25 |
let ww = u32(clamp(w, 0, i32(IN_W) - 1));
|
| 26 |
return f32(x[base + hh * IN_W + ww]);
|
| 27 |
}
|
| 28 |
+
{% if scalesBuffer %}
|
| 29 |
+
|
| 30 |
+
// The output shape condition floor(in * scale) == 2 * in also admits scales
|
| 31 |
+
// slightly above 2. Read the spatial scales: exactly 2 uses the stencils;
|
| 32 |
+
// otherwise sample asymmetric coordinates with source = output / scale.
|
| 33 |
+
fn general_sample(base: u32, oh: u32, ow: u32) -> f32 {
|
| 34 |
+
let sy = clamp(f32(oh) / f32(scales[2]), 0.0, f32(IN_H - 1u));
|
| 35 |
+
let sx = clamp(f32(ow) / f32(scales[3]), 0.0, f32(IN_W - 1u));
|
| 36 |
+
let y0 = i32(floor(sy));
|
| 37 |
+
let x0 = i32(floor(sx));
|
| 38 |
+
let y1 = min(y0 + 1, i32(IN_H) - 1);
|
| 39 |
+
let x1 = min(x0 + 1, i32(IN_W) - 1);
|
| 40 |
+
let same_y = y0 == y1;
|
| 41 |
+
let same_x = x0 == x1;
|
| 42 |
+
let wy0 = select(abs(sy - f32(y1)), 0.5, same_y);
|
| 43 |
+
let wy1 = select(abs(sy - f32(y0)), 0.5, same_y);
|
| 44 |
+
let wx0 = select(abs(sx - f32(x1)), 0.5, same_x);
|
| 45 |
+
let wx1 = select(abs(sx - f32(x0)), 0.5, same_x);
|
| 46 |
+
let v00 = input_at(base, y0, x0);
|
| 47 |
+
let v01 = input_at(base, y0, x1);
|
| 48 |
+
let v10 = input_at(base, y1, x0);
|
| 49 |
+
let v11 = input_at(base, y1, x1);
|
| 50 |
+
return wx0 * wy0 * v00 + wx1 * wy0 * v01 + wx0 * wy1 * v10 + wx1 * wy1 * v11;
|
| 51 |
+
}
|
| 52 |
+
{% endif %}
|
| 53 |
|
| 54 |
@compute @workgroup_size({{ wg }})
|
| 55 |
fn main(
|
|
|
|
| 57 |
@builtin(num_workgroups) nwg: vec3<u32>
|
| 58 |
) {
|
| 59 |
let stride = nwg.x * WG;
|
| 60 |
+
{% if scalesBuffer %}
|
| 61 |
+
let exact2x = f32(scales[2]) == 2.0 && f32(scales[3]) == 2.0;
|
| 62 |
+
{% endif %}
|
| 63 |
+
{% if vec4 %}
|
| 64 |
// Vec4 path: one thread owns 4 contiguous output columns (OUT_W % 4 == 0, so
|
| 65 |
// a 4-group never crosses a row). All 4 lanes share the same oh -> same source
|
| 66 |
// rows (y0,y1) and the same 4-column source window (qx0-1 .. qx0+2); load that
|
| 67 |
// window once, compute the 4 bilinear results with the baked per-lane parity
|
| 68 |
+
// weights, and emit a single vec4 store. Each component uses the same
|
| 69 |
+
// input_at calls, weight split, and f32 operation order.
|
| 70 |
let OUT_W4 = OUT_W / 4u;
|
| 71 |
let COUNT4 = COUNT / 4u;
|
| 72 |
for (var v = gid.x; v < COUNT4; v += stride) {
|
|
|
|
| 76 |
let nc = tt / OUT_H;
|
| 77 |
let base = nc * IN_HW;
|
| 78 |
let ow0 = ow4 * 4u;
|
| 79 |
+
{% if scalesBuffer %}
|
| 80 |
+
if (!exact2x) {
|
| 81 |
+
y[v] = vec4<{{ scalar }}>(vec4<f32>(
|
| 82 |
+
general_sample(base, oh, ow0),
|
| 83 |
+
general_sample(base, oh, ow0 + 1u),
|
| 84 |
+
general_sample(base, oh, ow0 + 2u),
|
| 85 |
+
general_sample(base, oh, ow0 + 3u)));
|
| 86 |
+
continue;
|
| 87 |
+
}
|
| 88 |
+
{% endif %}
|
| 89 |
let qx0 = i32(ow0 / 2u);
|
| 90 |
let y0 = i32(oh / 2u);
|
| 91 |
let wy = select(0.0, 0.5, (oh & 1u) == 1u);
|
|
|
|
| 110 |
let oh = t % OUT_H;
|
| 111 |
let nc = t / OUT_H;
|
| 112 |
let base = nc * IN_HW;
|
| 113 |
+
{% if scalesBuffer %}
|
| 114 |
+
if (!exact2x) {
|
| 115 |
+
y[i] = {{ scalar }}(general_sample(base, oh, ow));
|
| 116 |
+
continue;
|
| 117 |
+
}
|
| 118 |
+
{% endif %}
|
| 119 |
let qy = i32(oh / 2u);
|
| 120 |
let qx = i32(ow / 2u);
|
| 121 |
let oddY = (oh & 1u) == 1u;
|
build/webgpu/resize-nearest-integer-scale.wgsl.jinja
CHANGED
|
@@ -7,25 +7,22 @@
|
|
| 7 |
// ow = q*s + r gives an offset whose magnitude is strictly below one half, so
|
| 8 |
// ties cannot occur and every nearest rounding rule returns q. Downsampling and
|
| 9 |
// other coordinate-mode combinations require the generic coordinate transform.
|
| 10 |
-
{% if usesF16 %}
|
| 11 |
-
enable f16;
|
| 12 |
-
{% endif %}
|
| 13 |
{{ env.wgsl.resourceDeclarations }}
|
| 14 |
{% set wg = workgroupSize if workgroupSize is defined else tunables.WORKGROUP_SIZE %}
|
| 15 |
-
{%- set inH =
|
| 16 |
-
{% set inW =
|
| 17 |
-
{% set outH =
|
| 18 |
-
{% set outW =
|
| 19 |
{% set scaleW = ((outW / inW) | int) %}
|
| 20 |
-
{% set rows =
|
| 21 |
const WG: u32 = {{ wg }}u;
|
| 22 |
const IN_W: u32 = {{ inW }}u;
|
| 23 |
const IN_HW: u32 = {{ inH * inW }}u;
|
| 24 |
const OUT_H: u32 = {{ outH }}u;
|
| 25 |
const SCALE_H: u32 = {{ ((outH / inH) | int) }}u;
|
| 26 |
-
{% if not
|
| 27 |
{% endif %}
|
| 28 |
-
{% if
|
| 29 |
const OUT_W_VECS: u32 = {{ ((outW / 4) | int) }}u;
|
| 30 |
const COUNT: u32 = {{ (((rows * outW) / 4) | int) }}u;
|
| 31 |
{% else %}
|
|
@@ -39,7 +36,7 @@ fn main(
|
|
| 39 |
@builtin(num_workgroups) nwg: vec3<u32>
|
| 40 |
) {
|
| 41 |
let stride = nwg.x * WG;
|
| 42 |
-
{% if
|
| 43 |
for (var v = gid.x; v < COUNT; v += stride) {
|
| 44 |
let ow0 = (v % OUT_W_VECS) * 4u;
|
| 45 |
let t = v / OUT_W_VECS;
|
|
|
|
| 7 |
// ow = q*s + r gives an offset whose magnitude is strictly below one half, so
|
| 8 |
// ties cannot occur and every nearest rounding rule returns q. Downsampling and
|
| 9 |
// other coordinate-mode combinations require the generic coordinate transform.
|
|
|
|
|
|
|
|
|
|
| 10 |
{{ env.wgsl.resourceDeclarations }}
|
| 11 |
{% set wg = workgroupSize if workgroupSize is defined else tunables.WORKGROUP_SIZE %}
|
| 12 |
+
{%- set inH = xShape[2] %}
|
| 13 |
+
{% set inW = xShape[3] %}
|
| 14 |
+
{% set outH = yShape[2] %}
|
| 15 |
+
{% set outW = yShape[3] %}
|
| 16 |
{% set scaleW = ((outW / inW) | int) %}
|
| 17 |
+
{% set rows = yShape[0] * yShape[1] * outH %}
|
| 18 |
const WG: u32 = {{ wg }}u;
|
| 19 |
const IN_W: u32 = {{ inW }}u;
|
| 20 |
const IN_HW: u32 = {{ inH * inW }}u;
|
| 21 |
const OUT_H: u32 = {{ outH }}u;
|
| 22 |
const SCALE_H: u32 = {{ ((outH / inH) | int) }}u;
|
| 23 |
+
{% if not vec4 or scaleW == 3 %}const SCALE_W: u32 = {{ scaleW }}u;
|
| 24 |
{% endif %}
|
| 25 |
+
{% if vec4 %}
|
| 26 |
const OUT_W_VECS: u32 = {{ ((outW / 4) | int) }}u;
|
| 27 |
const COUNT: u32 = {{ (((rows * outW) / 4) | int) }}u;
|
| 28 |
{% else %}
|
|
|
|
| 36 |
@builtin(num_workgroups) nwg: vec3<u32>
|
| 37 |
) {
|
| 38 |
let stride = nwg.x * WG;
|
| 39 |
+
{% if vec4 %}
|
| 40 |
for (var v = gid.x; v < COUNT; v += stride) {
|
| 41 |
let ow0 = (v % OUT_W_VECS) * 4u;
|
| 42 |
let t = v / OUT_W_VECS;
|
build/webgpu/test.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
{
|
| 2 |
-
"op": "ai.onnx.Upsample",
|
| 3 |
"cases": [
|
| 4 |
{
|
| 5 |
"name": "nearest_2x_f32",
|
|
@@ -472,6 +471,38 @@
|
|
| 472 |
}
|
| 473 |
},
|
| 474 |
"outputs": { "y": { "dtype": "float32", "shape": [1, 1, 2, 1, 2, 1, 2, 6], "tolerance": 0 } }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
}
|
| 476 |
]
|
| 477 |
}
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"cases": [
|
| 3 |
{
|
| 4 |
"name": "nearest_2x_f32",
|
|
|
|
| 471 |
}
|
| 472 |
},
|
| 473 |
"outputs": { "y": { "dtype": "float32", "shape": [1, 1, 2, 1, 2, 1, 2, 6], "tolerance": 0 } }
|
| 474 |
+
},
|
| 475 |
+
{
|
| 476 |
+
"name": "linear_scale_2p1_not_exact_2x_f32",
|
| 477 |
+
"provenance": {
|
| 478 |
+
"notes": "scales 2.1 on a 3x3 input gives floor(3 * 2.1) = 6 outputs per axis, the same shape as an exact 2x upsample, but the asymmetric source coordinate is o / 2.1, not o / 2. A 2x stencil chosen from the shapes alone would compute the wrong interpolation weights."
|
| 479 |
+
},
|
| 480 |
+
"attrs": { "mode": "linear" },
|
| 481 |
+
"inputs": {
|
| 482 |
+
"x": {
|
| 483 |
+
"dtype": "float32",
|
| 484 |
+
"shape": [1, 1, 3, 3],
|
| 485 |
+
"data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] }
|
| 486 |
+
},
|
| 487 |
+
"scales": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, 1.0, 2.1, 2.1] } }
|
| 488 |
+
},
|
| 489 |
+
"outputs": { "y": { "dtype": "float32", "shape": [1, 1, 6, 6], "tolerance": 0.000001 } }
|
| 490 |
+
},
|
| 491 |
+
{
|
| 492 |
+
"name": "linear_scale_2p1_not_exact_2x_vec4_f32",
|
| 493 |
+
"provenance": {
|
| 494 |
+
"notes": "A 3-by-2 input with scales 2.1 produces a 6-by-4 vector-aligned output while retaining source coordinates based on 2.1 rather than 2."
|
| 495 |
+
},
|
| 496 |
+
"attrs": { "mode": "linear" },
|
| 497 |
+
"inputs": {
|
| 498 |
+
"x": {
|
| 499 |
+
"dtype": "float32",
|
| 500 |
+
"shape": [1, 1, 3, 2],
|
| 501 |
+
"data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] }
|
| 502 |
+
},
|
| 503 |
+
"scales": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, 1.0, 2.1, 2.1] } }
|
| 504 |
+
},
|
| 505 |
+
"outputs": { "y": { "dtype": "float32", "shape": [1, 1, 6, 4], "tolerance": 0.000001 } }
|
| 506 |
}
|
| 507 |
]
|
| 508 |
}
|