Xenova HF Staff commited on
Commit
dbcc68e
·
verified ·
1 Parent(s): 01356ce

sync 6fdf6301e2bb

Browse files
README.md CHANGED
@@ -49,6 +49,8 @@ Default values (overridable per request):
49
 
50
  One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
51
 
 
 
52
  - `ncdhw_rank5_channel_vector` — Shares volumetric coordinates, padding and interpolation across a vector of channels; uses two lanes for two channels and four lanes otherwise, with masked tails and device-capped workgroups.
53
  - `ncdhw_rank5` — Portable scalar volumetric sampling for all interpolation and padding modes.
54
 
@@ -57,14 +59,14 @@ One implementation is selected per call from the device capabilities, the reques
57
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
58
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
59
  - [`test.json`](build/webgpu/test.json) — correctness cases
60
- - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
61
  - [`grid-sample.wgsl.jinja`](build/webgpu/grid-sample.wgsl.jinja)
62
  - [`grid-sample3d.wgsl.jinja`](build/webgpu/grid-sample3d.wgsl.jinja)
63
 
64
  ## Use with `@huggingface/kernels`
65
 
66
  ```sh
67
- npm install --save-exact @huggingface/kernels@0.0.1-preview.2
68
  ```
69
 
70
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
 
49
 
50
  One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
51
 
52
+ - `nchw_rank4_channel_vector` — Resolves the sampling coordinate once per pixel and reuses it across four channels, so one thread gathers from four channel planes instead of reading one coalesced. This amortizes the coordinate math across channels, trading a coalesced read for a scattered gather; adapters where scattered gathers are slow use the coalesced variant instead.
53
+ - `nchw_rank4` — Portable scalar spatial sampling: one thread per output element, so reads stay coalesced within one channel plane. The route for every adapter whose gather throughput the channel vector would spend.
54
  - `ncdhw_rank5_channel_vector` — Shares volumetric coordinates, padding and interpolation across a vector of channels; uses two lanes for two channels and four lanes otherwise, with masked tails and device-capped workgroups.
55
  - `ncdhw_rank5` — Portable scalar volumetric sampling for all interpolation and padding modes.
56
 
 
59
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
60
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
61
  - [`test.json`](build/webgpu/test.json) — correctness cases
62
+ - [`bench.json`](build/webgpu/bench.json) — benchmark cases
63
  - [`grid-sample.wgsl.jinja`](build/webgpu/grid-sample.wgsl.jinja)
64
  - [`grid-sample3d.wgsl.jinja`](build/webgpu/grid-sample3d.wgsl.jinja)
65
 
66
  ## Use with `@huggingface/kernels`
67
 
68
  ```sh
69
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.3
70
  ```
71
 
72
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
build/webgpu/bench.json CHANGED
@@ -10,7 +10,7 @@
10
  "outputs": { "y": { "dtype": "float32", "shape": [1, 3, 256, 256] } }
11
  },
12
  {
13
- "name": "nchw_1x32x256x256_linear_zeros_f32_healthy",
14
  "preset": "smoke",
15
  "attrs": { "mode": "linear", "padding_mode": "zeros", "align_corners": 0 },
16
  "inputs": {
@@ -63,7 +63,7 @@
63
  }
64
  },
65
  {
66
- "name": "ncdhw_1x4x16x32x32_linear_zeros_f32_volumetric_unbenched_variant",
67
  "preset": "smoke",
68
  "attrs": { "mode": "linear", "padding_mode": "zeros", "align_corners": 0 },
69
  "inputs": {
@@ -412,6 +412,59 @@
412
  "bench": {
413
  "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.grid) + numel(shapes.y)) * 4" }]
414
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  }
416
- ]
 
417
  }
 
10
  "outputs": { "y": { "dtype": "float32", "shape": [1, 3, 256, 256] } }
11
  },
12
  {
13
+ "name": "nchw_1x32x256x256_linear_zeros_f32_control",
14
  "preset": "smoke",
15
  "attrs": { "mode": "linear", "padding_mode": "zeros", "align_corners": 0 },
16
  "inputs": {
 
63
  }
64
  },
65
  {
66
+ "name": "ncdhw_1x4x16x32x32_linear_zeros_f32_volumetric",
67
  "preset": "smoke",
68
  "attrs": { "mode": "linear", "padding_mode": "zeros", "align_corners": 0 },
69
  "inputs": {
 
412
  "bench": {
413
  "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.grid) + numel(shapes.y)) * 4" }]
414
  }
415
+ },
416
+ {
417
+ "name": "volume_scalar_linear_reflection_float32_c1",
418
+ "preset": "stress",
419
+ "attrs": { "mode": "linear", "padding_mode": "reflection", "align_corners": 0 },
420
+ "inputs": {
421
+ "x": { "dtype": "float32", "shape": [1, 1, 12, 16, 16], "dist": "normal", "scale": 0.3, "seed": 16304 },
422
+ "grid": { "dtype": "float32", "shape": [1, 24, 32, 32, 3], "dist": "normal", "scale": 1.5, "seed": 16404 }
423
+ },
424
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 24, 32, 32] } },
425
+ "bench": {
426
+ "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.grid) + numel(shapes.y)) * 4" }]
427
+ }
428
+ },
429
+ {
430
+ "name": "volume_scalar_linear_reflection_float16_c1",
431
+ "preset": "stress",
432
+ "attrs": { "mode": "linear", "padding_mode": "reflection", "align_corners": 1 },
433
+ "inputs": {
434
+ "x": { "dtype": "float16", "shape": [1, 1, 12, 16, 16], "dist": "normal", "scale": 0.3, "seed": 16305 },
435
+ "grid": { "dtype": "float16", "shape": [1, 24, 32, 32, 3], "dist": "normal", "scale": 1.5, "seed": 16405 }
436
+ },
437
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 1, 24, 32, 32] } },
438
+ "bench": {
439
+ "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.grid) + numel(shapes.y)) * 2" }]
440
+ }
441
+ },
442
+ {
443
+ "name": "volume_scalar_cubic_reflection_float32_c1",
444
+ "preset": "stress",
445
+ "attrs": { "mode": "cubic", "padding_mode": "reflection", "align_corners": 0 },
446
+ "inputs": {
447
+ "x": { "dtype": "float32", "shape": [1, 1, 12, 16, 16], "dist": "normal", "scale": 0.3, "seed": 16316 },
448
+ "grid": { "dtype": "float32", "shape": [1, 24, 32, 32, 3], "dist": "normal", "scale": 1.5, "seed": 16416 }
449
+ },
450
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 24, 32, 32] } },
451
+ "bench": {
452
+ "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.grid) + numel(shapes.y)) * 4" }]
453
+ }
454
+ },
455
+ {
456
+ "name": "volume_scalar_cubic_reflection_float16_c1",
457
+ "preset": "stress",
458
+ "attrs": { "mode": "cubic", "padding_mode": "reflection", "align_corners": 1 },
459
+ "inputs": {
460
+ "x": { "dtype": "float16", "shape": [1, 1, 12, 16, 16], "dist": "normal", "scale": 0.3, "seed": 16317 },
461
+ "grid": { "dtype": "float16", "shape": [1, 24, 32, 32, 3], "dist": "normal", "scale": 1.5, "seed": 16417 }
462
+ },
463
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 1, 24, 32, 32] } },
464
+ "bench": {
465
+ "metrics": [{ "type": "bandwidth", "value": "(numel(shapes.x) + numel(shapes.grid) + numel(shapes.y)) * 2" }]
466
+ }
467
  }
468
+ ],
469
+ "tunableSpace": { "RESOLVE_REFLECTION_TAPS": [0, 1] }
470
  }
build/webgpu/grid-sample.wgsl.jinja CHANGED
@@ -1,36 +1,8 @@
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
  fn denormalize(n: f32, length: u32) -> f32 {
@@ -72,8 +44,8 @@ fn reflect_coord(v: f32, lo: f32, hi: f32) -> f32 {
72
  }
73
  return x0;
74
  }
75
-
76
  {% endif %}
 
77
  {% if modeSpec != "nearest" %}
78
  fn sample_floor(v: f32) -> i32 {
79
  if (v > 2147483000.0) {
@@ -92,7 +64,7 @@ fn sample_floor(v: f32) -> i32 {
92
  }
93
  return i32(floor(v));
94
  }
95
- {%- else %}
96
  fn sample_round(v: f32) -> i32 {
97
  if (v > 2147483000.0) {
98
  {% if paddingMode == "reflection" %}
@@ -110,10 +82,9 @@ fn sample_round(v: f32) -> i32 {
110
  }
111
  return i32(round(v));
112
  }
113
- {%- endif %}
114
-
115
-
116
  {% if modeSpec == "cubic" %}
 
117
  fn cubic_coeffs(t: f32) -> vec4<f32> {
118
  let a = -0.75;
119
  let x0 = abs(t + 1.0);
@@ -128,9 +99,9 @@ fn cubic_one(x0: f32, a: f32) -> f32 {
128
  if (x0 < 2.0) { return a * x0 * x0 * x0 - 5.0 * a * x0 * x0 + 8.0 * a * x0 - 4.0 * a; }
129
  return 0.0;
130
  }
131
-
132
- {% endif -%}
133
  {% if channelWidthSpec is not defined %}
 
134
  fn pixel(base: u32, h: i32, w: i32) -> f32 {
135
  {% if paddingMode == "zeros" %}
136
  if (h < 0 || h >= i32(params.inH) || w < 0 || w >= i32(params.inW)) { return 0.0; }
@@ -152,12 +123,11 @@ fn pixel(base: u32, h: i32, w: i32) -> f32 {
152
  {% endif %}
153
  return f32(x[base + hh * params.inW + ww]);
154
  }
155
-
156
-
157
  {% endif %}
158
  {% if channelWidthSpec is defined %}
159
  {% set channelVec = "vec" ~ channelWidthSpec ~ "<f32>" %}
160
  {% set components = ["x", "y", "z", "w"] %}
 
161
  // A block of NCHW channels shares one grid coordinate. Tail lanes alias the
162
  // final channel safely because main suppresses their stores.
163
  {% if paddingMode != "reflection" or modeSpec == "nearest" %}
@@ -188,12 +158,9 @@ fn pixel_channels(n: u32, c0: u32, h: i32, w: i32) -> {{ channelVec }} {
188
  f32(x[(nb + {% if channelTail %}min(c0 + {{ lane }}u, params.C - 1u){% else %}c0 + {{ lane }}u{% endif %}) * plane + offset]){% if not loop.last %},{% else %});{% endif %}
189
  {% endfor %}
190
  }
191
-
192
- {% endif %}
193
- {% if paddingMode == "reflection" %}
194
  // Reflection gives every tap in one row or column the same resolved coordinate.
195
  // Resolve each row and column once in main and reuse it for all channel loads.
196
- {% if modeSpec != "nearest" %}
197
  fn pixel_channels_resolved(n: u32, c0: u32, h: u32, w: u32) -> {{ channelVec }} {
198
  let plane = params.inH * params.inW;
199
  let offset = h * params.inW + w;
@@ -203,15 +170,12 @@ fn pixel_channels_resolved(n: u32, c0: u32, h: u32, w: u32) -> {{ channelVec }}
203
  f32(x[(nb + {% if channelTail %}min(c0 + {{ lane }}u, params.C - 1u){% else %}c0 + {{ lane }}u{% endif %}) * plane + offset]){% if not loop.last %},{% else %});{% endif %}
204
  {% endfor %}
205
  }
206
-
207
  {% endif %}
208
- {% else %}
209
-
210
  {% endif %}
211
- {% endif -%}
212
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
213
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
214
- {{ flat_index_2d("i", "") }}
215
  {% if channelWidthSpec is defined %}
216
  let out_plane = params.outH * params.outW;
217
  let channel_blocks = (params.C + {{ channelWidthSpec - 1 }}u) / {{ channelWidthSpec }}u;
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
  // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};{% endmacro %}
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  {{ env.wgsl.resourceDeclarations }}
7
 
8
  fn denormalize(n: f32, length: u32) -> f32 {
 
44
  }
45
  return x0;
46
  }
 
47
  {% endif %}
48
+
49
  {% if modeSpec != "nearest" %}
50
  fn sample_floor(v: f32) -> i32 {
51
  if (v > 2147483000.0) {
 
64
  }
65
  return i32(floor(v));
66
  }
67
+ {% else %}
68
  fn sample_round(v: f32) -> i32 {
69
  if (v > 2147483000.0) {
70
  {% if paddingMode == "reflection" %}
 
82
  }
83
  return i32(round(v));
84
  }
85
+ {% endif %}
 
 
86
  {% if modeSpec == "cubic" %}
87
+
88
  fn cubic_coeffs(t: f32) -> vec4<f32> {
89
  let a = -0.75;
90
  let x0 = abs(t + 1.0);
 
99
  if (x0 < 2.0) { return a * x0 * x0 * x0 - 5.0 * a * x0 * x0 + 8.0 * a * x0 - 4.0 * a; }
100
  return 0.0;
101
  }
102
+ {% endif %}
 
103
  {% if channelWidthSpec is not defined %}
104
+
105
  fn pixel(base: u32, h: i32, w: i32) -> f32 {
106
  {% if paddingMode == "zeros" %}
107
  if (h < 0 || h >= i32(params.inH) || w < 0 || w >= i32(params.inW)) { return 0.0; }
 
123
  {% endif %}
124
  return f32(x[base + hh * params.inW + ww]);
125
  }
 
 
126
  {% endif %}
127
  {% if channelWidthSpec is defined %}
128
  {% set channelVec = "vec" ~ channelWidthSpec ~ "<f32>" %}
129
  {% set components = ["x", "y", "z", "w"] %}
130
+
131
  // A block of NCHW channels shares one grid coordinate. Tail lanes alias the
132
  // final channel safely because main suppresses their stores.
133
  {% if paddingMode != "reflection" or modeSpec == "nearest" %}
 
158
  f32(x[(nb + {% if channelTail %}min(c0 + {{ lane }}u, params.C - 1u){% else %}c0 + {{ lane }}u{% endif %}) * plane + offset]){% if not loop.last %},{% else %});{% endif %}
159
  {% endfor %}
160
  }
161
+ {% else %}
 
 
162
  // Reflection gives every tap in one row or column the same resolved coordinate.
163
  // Resolve each row and column once in main and reuse it for all channel loads.
 
164
  fn pixel_channels_resolved(n: u32, c0: u32, h: u32, w: u32) -> {{ channelVec }} {
165
  let plane = params.inH * params.inW;
166
  let offset = h * params.inW + w;
 
170
  f32(x[(nb + {% if channelTail %}min(c0 + {{ lane }}u, params.C - 1u){% else %}c0 + {{ lane }}u{% endif %}) * plane + offset]){% if not loop.last %},{% else %});{% endif %}
171
  {% endfor %}
172
  }
 
173
  {% endif %}
 
 
174
  {% endif %}
175
+
176
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
177
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
178
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "i", "") }}
179
  {% if channelWidthSpec is defined %}
180
  let out_plane = params.outH * params.outW;
181
  let channel_blocks = (params.C + {{ channelWidthSpec - 1 }}u) / {{ channelWidthSpec }}u;
build/webgpu/grid-sample3d.wgsl.jinja CHANGED
@@ -1,36 +1,9 @@
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
  fn denormalize(n: f32, length: u32) -> f32 {
@@ -72,8 +45,8 @@ fn reflect_coord(v: f32, lo: f32, hi: f32) -> f32 {
72
  }
73
  return x0;
74
  }
75
-
76
  {% endif %}
 
77
  {% if modeSpec != "nearest" %}
78
  fn sample_floor(v: f32) -> i32 {
79
  if (v > 2147483000.0) {
@@ -92,7 +65,7 @@ fn sample_floor(v: f32) -> i32 {
92
  }
93
  return i32(floor(v));
94
  }
95
- {%- else %}
96
  fn sample_round(v: f32) -> i32 {
97
  if (v > 2147483000.0) {
98
  {% if paddingMode == "reflection" %}
@@ -110,10 +83,9 @@ fn sample_round(v: f32) -> i32 {
110
  }
111
  return i32(round(v));
112
  }
113
- {%- endif %}
114
-
115
-
116
  {% if modeSpec == "cubic" %}
 
117
  fn cubic_coeffs(t: f32) -> vec4<f32> {
118
  let a = -0.75;
119
  let x0 = abs(t + 1.0);
@@ -128,12 +100,34 @@ fn cubic_one(x0: f32, a: f32) -> f32 {
128
  if (x0 < 2.0) { return a * x0 * x0 * x0 - 5.0 * a * x0 * x0 + 8.0 * a * x0 - 4.0 * a; }
129
  return 0.0;
130
  }
131
-
132
- {% endif -%}
133
  {% set volumeVector = channelWidthSpec is defined %}
134
  {% set volumeT = "vec" ~ channelWidthSpec ~ "<f32>" if volumeVector else "f32" %}
135
  {% set voxelArgs = "img_base, c, " if volumeVector and channelTail else "img_base, " %}
136
- fn voxel(base: u32,{% if volumeVector and channelTail %} c0: u32,{% endif %} d: i32, h: i32, w: i32) -> {{ volumeT }} {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  {% if paddingMode == "zeros" %}
138
  if (d < 0 || d >= i32(params.inD) || h < 0 || h >= i32(params.inH) || w < 0 || w >= i32(params.inW)) {
139
  return {% if volumeVector %}{{ volumeT }}(0.0){% else %}0.0{% endif %};
@@ -159,6 +153,7 @@ fn voxel(base: u32,{% if volumeVector and channelTail %} c0: u32,{% endif %} d:
159
  let hh = u32(clamp(rh, 0, i32(params.inH) - 1));
160
  let ww = u32(clamp(rw, 0, i32(params.inW) - 1));
161
  {% endif %}
 
162
  {% if volumeVector %}
163
  // All channels share the resolved voxel coordinates. Incomplete vectors
164
  // alias the final channel on loads; main masks their output stores.
@@ -173,7 +168,7 @@ fn voxel(base: u32,{% if volumeVector and channelTail %} c0: u32,{% endif %} d:
173
 
174
  @compute @workgroup_size({{ volumeWorkgroup if volumeVector else tunables.WORKGROUP_SIZE }})
175
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
176
- {{ flat_index_2d(guardInline=true) }}
177
 
178
  let ow = i % params.outW;
179
  var t = i / params.outW;
@@ -200,22 +195,23 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
200
  let result = voxel({{ voxelArgs }}sample_round(sz), sample_round(sy), sample_round(sx));
201
  {% elif modeSpec == "cubic" %}
202
  // Tricubic applies separable four-tap Keys interpolation along each spatial
203
- // axis; voxel() already resolves every padding mode, so
204
- // the 64-tap window needs no boundary handling of its own.
205
  let x0 = sample_floor(sx) - 1;
206
  let y0 = sample_floor(sy) - 1;
207
  let z0 = sample_floor(sz) - 1;
208
  let cx = cubic_coeffs(sx - f32(x0 + 1));
209
  let cy = cubic_coeffs(sy - f32(y0 + 1));
210
  let cz = cubic_coeffs(sz - f32(z0 + 1));
 
211
  var result = {% if volumeVector %}{{ volumeT }}(0.0){% else %}0.0{% endif %};
212
  for (var kz = 0i; kz < 4i; kz = kz + 1i) {
213
  var plane = {% if volumeVector %}{{ volumeT }}(0.0){% else %}0.0{% endif %};
214
  for (var ky = 0i; ky < 4i; ky = ky + 1i) {
215
- let row = cx.x * voxel({{ voxelArgs }}z0 + kz, y0 + ky, x0)
216
- + cx.y * voxel({{ voxelArgs }}z0 + kz, y0 + ky, x0 + 1)
217
- + cx.z * voxel({{ voxelArgs }}z0 + kz, y0 + ky, x0 + 2)
218
- + cx.w * voxel({{ voxelArgs }}z0 + kz, y0 + ky, x0 + 3);
219
  plane = plane + cy[u32(ky)] * row;
220
  }
221
  result = result + cz[u32(kz)] * plane;
@@ -227,14 +223,15 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
227
  let wx = sx - f32(x0);
228
  let wy = sy - f32(y0);
229
  let wz = sz - f32(z0);
230
- let v000 = voxel({{ voxelArgs }}z0, y0, x0);
231
- let v001 = voxel({{ voxelArgs }}z0, y0, x0 + 1);
232
- let v010 = voxel({{ voxelArgs }}z0, y0 + 1, x0);
233
- let v011 = voxel({{ voxelArgs }}z0, y0 + 1, x0 + 1);
234
- let v100 = voxel({{ voxelArgs }}z0 + 1, y0, x0);
235
- let v101 = voxel({{ voxelArgs }}z0 + 1, y0, x0 + 1);
236
- let v110 = voxel({{ voxelArgs }}z0 + 1, y0 + 1, x0);
237
- let v111 = voxel({{ voxelArgs }}z0 + 1, y0 + 1, x0 + 1);
 
238
  let front = (1.0 - wy) * ((1.0 - wx) * v000 + wx * v001) + wy * ((1.0 - wx) * v010 + wx * v011);
239
  let back = (1.0 - wy) * ((1.0 - wx) * v100 + wx * v101) + wy * ((1.0 - wx) * v110 + wx * v111);
240
  let result = (1.0 - wz) * front + wz * back;
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
  // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
6
+ if ({{ name }} >= {{ bound }}) { return; }{% endmacro %}
 
 
 
 
 
 
 
 
 
 
 
 
7
  {{ env.wgsl.resourceDeclarations }}
8
 
9
  fn denormalize(n: f32, length: u32) -> f32 {
 
45
  }
46
  return x0;
47
  }
 
48
  {% endif %}
49
+
50
  {% if modeSpec != "nearest" %}
51
  fn sample_floor(v: f32) -> i32 {
52
  if (v > 2147483000.0) {
 
65
  }
66
  return i32(floor(v));
67
  }
68
+ {% else %}
69
  fn sample_round(v: f32) -> i32 {
70
  if (v > 2147483000.0) {
71
  {% if paddingMode == "reflection" %}
 
83
  }
84
  return i32(round(v));
85
  }
86
+ {% endif %}
 
 
87
  {% if modeSpec == "cubic" %}
88
+
89
  fn cubic_coeffs(t: f32) -> vec4<f32> {
90
  let a = -0.75;
91
  let x0 = abs(t + 1.0);
 
100
  if (x0 < 2.0) { return a * x0 * x0 * x0 - 5.0 * a * x0 * x0 + 8.0 * a * x0 - 4.0 * a; }
101
  return 0.0;
102
  }
103
+ {% endif %}
 
104
  {% set volumeVector = channelWidthSpec is defined %}
105
  {% set volumeT = "vec" ~ channelWidthSpec ~ "<f32>" if volumeVector else "f32" %}
106
  {% set voxelArgs = "img_base, c, " if volumeVector and channelTail else "img_base, " %}
107
+ {% if resolveReflectionTaps %}
108
+
109
+ fn reflected_index(index: i32, length: u32) -> u32 {
110
+ {% if alignCorners %}
111
+ let reflected = i32(reflect_coord(f32(index), 0.0, f32(length) - 1.0));
112
+ {% else %}
113
+ let reflected = i32(reflect_coord(f32(index), -0.5, f32(length) - 0.5));
114
+ {% endif %}
115
+ return u32(clamp(reflected, 0, i32(length) - 1));
116
+ }
117
+ {% endif %}
118
+ {% macro reflected_axis_taps() %}
119
+ {% if resolveReflectionTaps %}
120
+ {% for axis, length in [["x", "inW"], ["y", "inH"], ["z", "inD"]] %}
121
+ let i{{ axis }} = vec{{ 4 if modeSpec == "cubic" else 2 }}<u32>(
122
+ {% for tap in range(4 if modeSpec == "cubic" else 2) %}
123
+ reflected_index({{ axis }}0{% if tap %} + {{ tap }}{% endif %}, params.{{ length }}){% if loop.last %});{% else %},{% endif %}
124
+ {% endfor %}
125
+ {% endfor %}
126
+ {% endif %}
127
+ {% endmacro %}
128
+
129
+ fn voxel(base: u32,{% if volumeVector and channelTail %} c0: u32,{% endif %} {% if resolveReflectionTaps %}dd: u32, hh: u32, ww: u32{% else %}d: i32, h: i32, w: i32{% endif %}) -> {{ volumeT }} {
130
+ {% if not resolveReflectionTaps %}
131
  {% if paddingMode == "zeros" %}
132
  if (d < 0 || d >= i32(params.inD) || h < 0 || h >= i32(params.inH) || w < 0 || w >= i32(params.inW)) {
133
  return {% if volumeVector %}{{ volumeT }}(0.0){% else %}0.0{% endif %};
 
153
  let hh = u32(clamp(rh, 0, i32(params.inH) - 1));
154
  let ww = u32(clamp(rw, 0, i32(params.inW) - 1));
155
  {% endif %}
156
+ {% endif %}
157
  {% if volumeVector %}
158
  // All channels share the resolved voxel coordinates. Incomplete vectors
159
  // alias the final channel on loads; main masks their output stores.
 
168
 
169
  @compute @workgroup_size({{ volumeWorkgroup if volumeVector else tunables.WORKGROUP_SIZE }})
170
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
171
+ {{ flat_index_2d(volumeWorkgroup if volumeVector else tunables.WORKGROUP_SIZE, guardInline=true) }}
172
 
173
  let ow = i % params.outW;
174
  var t = i / params.outW;
 
195
  let result = voxel({{ voxelArgs }}sample_round(sz), sample_round(sy), sample_round(sx));
196
  {% elif modeSpec == "cubic" %}
197
  // Tricubic applies separable four-tap Keys interpolation along each spatial
198
+ // axis. Reflection taps are resolved once per axis when enabled; other
199
+ // padding modes are handled by voxel().
200
  let x0 = sample_floor(sx) - 1;
201
  let y0 = sample_floor(sy) - 1;
202
  let z0 = sample_floor(sz) - 1;
203
  let cx = cubic_coeffs(sx - f32(x0 + 1));
204
  let cy = cubic_coeffs(sy - f32(y0 + 1));
205
  let cz = cubic_coeffs(sz - f32(z0 + 1));
206
+ {{ reflected_axis_taps() }}
207
  var result = {% if volumeVector %}{{ volumeT }}(0.0){% else %}0.0{% endif %};
208
  for (var kz = 0i; kz < 4i; kz = kz + 1i) {
209
  var plane = {% if volumeVector %}{{ volumeT }}(0.0){% else %}0.0{% endif %};
210
  for (var ky = 0i; ky < 4i; ky = ky + 1i) {
211
+ let row = cx.x * voxel({{ voxelArgs }}{{ "iz[u32(kz)], iy[u32(ky)], ix.x" if resolveReflectionTaps else "z0 + kz, y0 + ky, x0" }})
212
+ + cx.y * voxel({{ voxelArgs }}{{ "iz[u32(kz)], iy[u32(ky)], ix.y" if resolveReflectionTaps else "z0 + kz, y0 + ky, x0 + 1" }})
213
+ + cx.z * voxel({{ voxelArgs }}{{ "iz[u32(kz)], iy[u32(ky)], ix.z" if resolveReflectionTaps else "z0 + kz, y0 + ky, x0 + 2" }})
214
+ + cx.w * voxel({{ voxelArgs }}{{ "iz[u32(kz)], iy[u32(ky)], ix.w" if resolveReflectionTaps else "z0 + kz, y0 + ky, x0 + 3" }});
215
  plane = plane + cy[u32(ky)] * row;
216
  }
217
  result = result + cz[u32(kz)] * plane;
 
223
  let wx = sx - f32(x0);
224
  let wy = sy - f32(y0);
225
  let wz = sz - f32(z0);
226
+ {{ reflected_axis_taps() }}
227
+ let v000 = voxel({{ voxelArgs }}{{ "iz.x, iy.x, ix.x" if resolveReflectionTaps else "z0, y0, x0" }});
228
+ let v001 = voxel({{ voxelArgs }}{{ "iz.x, iy.x, ix.y" if resolveReflectionTaps else "z0, y0, x0 + 1" }});
229
+ let v010 = voxel({{ voxelArgs }}{{ "iz.x, iy.y, ix.x" if resolveReflectionTaps else "z0, y0 + 1, x0" }});
230
+ let v011 = voxel({{ voxelArgs }}{{ "iz.x, iy.y, ix.y" if resolveReflectionTaps else "z0, y0 + 1, x0 + 1" }});
231
+ let v100 = voxel({{ voxelArgs }}{{ "iz.y, iy.x, ix.x" if resolveReflectionTaps else "z0 + 1, y0, x0" }});
232
+ let v101 = voxel({{ voxelArgs }}{{ "iz.y, iy.x, ix.y" if resolveReflectionTaps else "z0 + 1, y0, x0 + 1" }});
233
+ let v110 = voxel({{ voxelArgs }}{{ "iz.y, iy.y, ix.x" if resolveReflectionTaps else "z0 + 1, y0 + 1, x0" }});
234
+ let v111 = voxel({{ voxelArgs }}{{ "iz.y, iy.y, ix.y" if resolveReflectionTaps else "z0 + 1, y0 + 1, x0 + 1" }});
235
  let front = (1.0 - wy) * ((1.0 - wx) * v000 + wx * v001) + wy * ((1.0 - wx) * v010 + wx * v011);
236
  let back = (1.0 - wy) * ((1.0 - wx) * v100 + wx * v101) + wy * ((1.0 - wx) * v110 + wx * v111);
237
  let result = (1.0 - wz) * front + wz * back;
build/webgpu/manifest.json CHANGED
@@ -22,8 +22,9 @@
22
  "align_corners": { "values": [0, 1] }
23
  },
24
  "typeConstraints": { "T": ["float32", "float16"] },
25
- "tunables": { "WORKGROUP_SIZE": { "default": 256 } },
26
  "derive": {
 
27
  "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
28
  "reportedNonWave32Adapter": "not wave32Adapter and (has(device.adapterInfo, \"subgroupMinSize\") or has(device.adapterInfo, \"subgroupMaxSize\"))",
29
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
@@ -31,11 +32,13 @@
31
  "channelWidth": 4,
32
  "scalar": "dtypes.T",
33
  "volumeWidth": "min(channelWidth, pow(2, log2ceil(max(1, dim(shapes.x, 1)))))",
34
- "volumeWorkgroup": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)"
 
 
 
35
  },
36
  "bindings": {
37
  "params": {
38
- "buffer": "uniform",
39
  "struct": [
40
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" },
41
  { "name": "C", "type": "u32", "value": "dim(shapes.x, 1)" },
@@ -50,19 +53,14 @@
50
  {
51
  "id": "nchw_rank4_channel_vector",
52
  "priority": 5,
53
- "when": ["rank4Ok", "not reportedNonWave32Adapter", "dim(shapes.x, 1) >= 2"],
 
54
  "passes": [
55
  {
56
  "id": "main",
57
  "name": "GridSample.ChannelX4",
58
  "shader": "grid-sample.wgsl.jinja",
59
- "derive": {
60
- "modeSpec": "attrs.mode",
61
- "paddingMode": "attrs.padding_mode",
62
- "alignCorners": "attrs.align_corners != 0",
63
- "channelWidthSpec": "channelWidth",
64
- "channelTail": "dim(shapes.x, 1) % channelWidth != 0"
65
- },
66
  "bindings": ["x", "grid", "y", "params"],
67
  "dispatch": {
68
  "x": "min(ceilDiv((dim(shapes.y, 0) * ceilDiv(dim(shapes.y, 1), channelWidth) * dim(shapes.y, 2) * dim(shapes.y, 3)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -80,11 +78,6 @@
80
  "id": "main",
81
  "name": "GridSample",
82
  "shader": "grid-sample.wgsl.jinja",
83
- "derive": {
84
- "modeSpec": "attrs.mode",
85
- "paddingMode": "attrs.padding_mode",
86
- "alignCorners": "attrs.align_corners != 0"
87
- },
88
  "bindings": ["x", "grid", "y", "params"],
89
  "dispatch": {
90
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -104,11 +97,9 @@
104
  "name": "GridSample.VolumetricChannels",
105
  "shader": "grid-sample3d.wgsl.jinja",
106
  "derive": {
107
- "modeSpec": "attrs.mode",
108
- "paddingMode": "attrs.padding_mode",
109
- "alignCorners": "attrs.align_corners != 0",
110
  "channelWidthSpec": "volumeWidth",
111
- "channelTail": "dim(shapes.x, 1) % volumeWidth != 0"
 
112
  },
113
  "bindings": [
114
  "x",
@@ -150,9 +141,7 @@
150
  "name": "GridSample.Volumetric",
151
  "shader": "grid-sample3d.wgsl.jinja",
152
  "derive": {
153
- "modeSpec": "attrs.mode",
154
- "paddingMode": "attrs.padding_mode",
155
- "alignCorners": "attrs.align_corners != 0"
156
  },
157
  "bindings": [
158
  "x",
 
22
  "align_corners": { "values": [0, 1] }
23
  },
24
  "typeConstraints": { "T": ["float32", "float16"] },
25
+ "tunables": { "WORKGROUP_SIZE": { "default": 256 }, "RESOLVE_REFLECTION_TAPS": { "default": 1 } },
26
  "derive": {
27
+ "appleAdapter": "has(device.adapterInfo, \"vendor\") and device.adapterInfo.vendor == \"apple\"",
28
  "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
29
  "reportedNonWave32Adapter": "not wave32Adapter and (has(device.adapterInfo, \"subgroupMinSize\") or has(device.adapterInfo, \"subgroupMaxSize\"))",
30
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
 
32
  "channelWidth": 4,
33
  "scalar": "dtypes.T",
34
  "volumeWidth": "min(channelWidth, pow(2, log2ceil(max(1, dim(shapes.x, 1)))))",
35
+ "volumeWorkgroup": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
36
+ "alignCorners": "attrs.align_corners != 0",
37
+ "modeSpec": "attrs.mode",
38
+ "paddingMode": "attrs.padding_mode"
39
  },
40
  "bindings": {
41
  "params": {
 
42
  "struct": [
43
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" },
44
  { "name": "C", "type": "u32", "value": "dim(shapes.x, 1)" },
 
53
  {
54
  "id": "nchw_rank4_channel_vector",
55
  "priority": 5,
56
+ "when": ["rank4Ok", "dim(shapes.x, 1) >= 2"],
57
+ "demoteWhen": ["not appleAdapter"],
58
  "passes": [
59
  {
60
  "id": "main",
61
  "name": "GridSample.ChannelX4",
62
  "shader": "grid-sample.wgsl.jinja",
63
+ "derive": { "channelWidthSpec": "channelWidth", "channelTail": "dim(shapes.x, 1) % channelWidth != 0" },
 
 
 
 
 
 
64
  "bindings": ["x", "grid", "y", "params"],
65
  "dispatch": {
66
  "x": "min(ceilDiv((dim(shapes.y, 0) * ceilDiv(dim(shapes.y, 1), channelWidth) * dim(shapes.y, 2) * dim(shapes.y, 3)), (tunables.WORKGROUP_SIZE)), 65535)",
 
78
  "id": "main",
79
  "name": "GridSample",
80
  "shader": "grid-sample.wgsl.jinja",
 
 
 
 
 
81
  "bindings": ["x", "grid", "y", "params"],
82
  "dispatch": {
83
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
97
  "name": "GridSample.VolumetricChannels",
98
  "shader": "grid-sample3d.wgsl.jinja",
99
  "derive": {
 
 
 
100
  "channelWidthSpec": "volumeWidth",
101
+ "channelTail": "dim(shapes.x, 1) % volumeWidth != 0",
102
+ "resolveReflectionTaps": "attrs.padding_mode == \"reflection\" and attrs.mode != \"nearest\" and tunables.RESOLVE_REFLECTION_TAPS != 0"
103
  },
104
  "bindings": [
105
  "x",
 
141
  "name": "GridSample.Volumetric",
142
  "shader": "grid-sample3d.wgsl.jinja",
143
  "derive": {
144
+ "resolveReflectionTaps": "attrs.padding_mode == \"reflection\" and attrs.mode != \"nearest\" and tunables.RESOLVE_REFLECTION_TAPS != 0"
 
 
145
  },
146
  "bindings": [
147
  "x",
build/webgpu/metadata.json CHANGED
@@ -1,22 +1,22 @@
1
  {
2
  "name": "ai.onnx.GridSample",
3
- "id": "_ai_onnx_gridsample_webgpu_f340775",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "GKt3XLyZG7PU01DKekKBLzn6wJaj5/4VX+430PWZLas=",
11
- "grid-sample.wgsl.jinja": "azjm8+LAvCxuA3CAJtB0ElRwBbDCOy05cOL8rJzeeK0=",
12
- "grid-sample3d.wgsl.jinja": "zLErkeaeWt4gEn6iAkcEYGDEhauXCj+czW4rO0kbv2k=",
13
- "manifest.json": "PcNyEEhLUbrU5SY4T6mBxI/MFk1fnH4BQWcpSnleDHU=",
14
- "test.json": "mLgrc4Eep+6yiX6JmU0Hpt/dMAjXnL6kxOFsL+/hO38="
15
  }
16
  },
17
- "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
18
  "webgpu": {
19
- "manifestSpec": "2.0",
20
  "variants": {
21
  "nchw_rank4_channel_vector": ["grid-sample.wgsl.jinja"],
22
  "nchw_rank4": ["grid-sample.wgsl.jinja"],
 
1
  {
2
  "name": "ai.onnx.GridSample",
3
+ "id": "_ai_onnx_gridsample_webgpu_11c7e55",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "zRyVJ7WL0vHjv1U48z8EM9u2l5FufDQYPq5VfWYxVyw=",
11
+ "grid-sample.wgsl.jinja": "+smIQHwp1GEltoAVpbIKI0oJbsjAXGVGPy2ZhVHvF8Y=",
12
+ "grid-sample3d.wgsl.jinja": "e638b7ZNv0/EmEXA5+M4PlzQ81vHXpRlj3t5iVieohY=",
13
+ "manifest.json": "UyKUdbD0viJTcRHht1BWQpHNviVreaI5JZjLthQDKM0=",
14
+ "test.json": "5SMIjxyMjqQYJ+GGIjZ6md6MjqRGQwkKIbZn5NAIGRw="
15
  }
16
  },
17
+ "provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
18
  "webgpu": {
19
+ "manifestSpec": "2.1",
20
  "variants": {
21
  "nchw_rank4_channel_vector": ["grid-sample.wgsl.jinja"],
22
  "nchw_rank4": ["grid-sample.wgsl.jinja"],
build/webgpu/test.json CHANGED
@@ -5,7 +5,8 @@
5
  "onnx_backend_far_coords_zeros_padding_input_grid": [-10, -10, -5, -5, -0.2, -0.2, 10, 10, 10, 10, -0.2, -0.2, 5, 5, 10, 10],
6
  "onnx_backend_gridsample_input_x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
7
  "onnx_backend_gridsample_aligncorners_true_input_grid": [-1, -1, -0.5, -0.5, -0.20000000298023224, -0.20000000298023224, 0, 0, 0, 0, -0.20000000298023224, -0.20000000298023224, 0.5, 0.5, 1, 1],
8
- "onnx_backend_gridsample_border_padding_input_grid": [-10, -10, -5, -5, -0.20000000298023224, -0.20000000298023224, 10, 10, 10, 10, -0.20000000298023224, -0.20000000298023224, 5, 5, 10, 10]
 
9
  },
10
  "cases": [
11
  {
@@ -227,7 +228,7 @@
227
  "provenance": {
228
  "source": "onnxruntime/test/providers/cpu/tensor/grid_sample_test_custom.cc",
229
  "test": "GridSampleCustomTest.test_grid_sample_20_4D_bilinear_reflection_extreme_coords",
230
- "notes": "Uses smaller finite coordinates than ORT's extreme-coordinate regression while preserving the reflection boundary path."
231
  },
232
  "attrs": { "mode": "linear", "padding_mode": "reflection", "align_corners": 0 },
233
  "inputs": {
@@ -2739,6 +2740,312 @@
2739
  }
2740
  },
2741
  "outputs": { "y": { "dtype": "float32", "shape": [2, 5, 5, 7, 9], "tolerance": 0.0001, "relTolerance": 0.0001 } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2742
  }
2743
  ]
2744
  }
 
5
  "onnx_backend_far_coords_zeros_padding_input_grid": [-10, -10, -5, -5, -0.2, -0.2, 10, 10, 10, 10, -0.2, -0.2, 5, 5, 10, 10],
6
  "onnx_backend_gridsample_input_x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
7
  "onnx_backend_gridsample_aligncorners_true_input_grid": [-1, -1, -0.5, -0.5, -0.20000000298023224, -0.20000000298023224, 0, 0, 0, 0, -0.20000000298023224, -0.20000000298023224, 0.5, 0.5, 1, 1],
8
+ "onnx_backend_gridsample_border_padding_input_grid": [-10, -10, -5, -5, -0.20000000298023224, -0.20000000298023224, 10, 10, 10, 10, -0.20000000298023224, -0.20000000298023224, 5, 5, 10, 10],
9
+ "reflection_coordinate_f32_rounding_rank4_input_x": [1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1]
10
  },
11
  "cases": [
12
  {
 
228
  "provenance": {
229
  "source": "onnxruntime/test/providers/cpu/tensor/grid_sample_test_custom.cc",
230
  "test": "GridSampleCustomTest.test_grid_sample_20_4D_bilinear_reflection_extreme_coords",
231
+ "notes": "Grid coordinates of +/-5, well outside the normalized [-1,1] range but smaller than ONNX Runtime's extreme-coordinate test values, check reflection padding at finite far-out-of-bounds coordinates with bilinear sampling."
232
  },
233
  "attrs": { "mode": "linear", "padding_mode": "reflection", "align_corners": 0 },
234
  "inputs": {
 
2740
  }
2741
  },
2742
  "outputs": { "y": { "dtype": "float32", "shape": [2, 5, 5, 7, 9], "tolerance": 0.0001, "relTolerance": 0.0001 } }
2743
+ },
2744
+ {
2745
+ "name": "volume_scalar_linear_reflection_ac0_float32",
2746
+ "provenance": {
2747
+ "notes": "Scalar volumetric reflection with out-of-range taps; aligned cases include a dimension-one input to exercise the zero-width reflection interval."
2748
+ },
2749
+ "attrs": { "mode": "linear", "padding_mode": "reflection", "align_corners": 0 },
2750
+ "inputs": {
2751
+ "x": {
2752
+ "dtype": "float32",
2753
+ "shape": [2, 1, 3, 5, 7],
2754
+ "data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.17, "cosStep": 0.09 }
2755
+ },
2756
+ "grid": {
2757
+ "dtype": "float32",
2758
+ "shape": [2, 3, 4, 7, 3],
2759
+ "data": { "kind": "linspace", "start": -3.5, "end": 3.5 }
2760
+ }
2761
+ },
2762
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 1, 3, 4, 7], "tolerance": 0.0001, "relTolerance": 0.0001 } }
2763
+ },
2764
+ {
2765
+ "name": "volume_scalar_linear_reflection_ac1_float32",
2766
+ "provenance": {
2767
+ "notes": "Scalar volumetric reflection with out-of-range taps; aligned cases include a dimension-one input to exercise the zero-width reflection interval."
2768
+ },
2769
+ "attrs": { "mode": "linear", "padding_mode": "reflection", "align_corners": 1 },
2770
+ "inputs": {
2771
+ "x": {
2772
+ "dtype": "float32",
2773
+ "shape": [2, 1, 1, 5, 7],
2774
+ "data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.17, "cosStep": 0.09 }
2775
+ },
2776
+ "grid": {
2777
+ "dtype": "float32",
2778
+ "shape": [2, 3, 4, 7, 3],
2779
+ "data": { "kind": "linspace", "start": -3.5, "end": 3.5 }
2780
+ }
2781
+ },
2782
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 1, 3, 4, 7], "tolerance": 0.0001, "relTolerance": 0.0001 } }
2783
+ },
2784
+ {
2785
+ "name": "volume_tail_linear_reflection_float32_per_voxel",
2786
+ "provenance": {
2787
+ "notes": "Explicit per-voxel comparison path retains the reflection behavior on incomplete channel vectors."
2788
+ },
2789
+ "attrs": { "mode": "linear", "padding_mode": "reflection", "align_corners": 0 },
2790
+ "inputs": {
2791
+ "x": {
2792
+ "dtype": "float32",
2793
+ "shape": [2, 3, 5, 7, 9],
2794
+ "data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.17, "cosStep": 0.09 }
2795
+ },
2796
+ "grid": {
2797
+ "dtype": "float32",
2798
+ "shape": [2, 7, 9, 11, 3],
2799
+ "data": { "kind": "linspace", "start": -3.5, "end": 3.5 }
2800
+ }
2801
+ },
2802
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 7, 9, 11], "tolerance": 0.0001, "relTolerance": 0.0001 } },
2803
+ "tunables": { "RESOLVE_REFLECTION_TAPS": 0 }
2804
+ },
2805
+ {
2806
+ "name": "volume_scalar_linear_reflection_ac0_float16",
2807
+ "provenance": {
2808
+ "notes": "Scalar volumetric reflection with out-of-range taps; aligned cases include a dimension-one input to exercise the zero-width reflection interval."
2809
+ },
2810
+ "attrs": { "mode": "linear", "padding_mode": "reflection", "align_corners": 0 },
2811
+ "inputs": {
2812
+ "x": {
2813
+ "dtype": "float16",
2814
+ "shape": [2, 1, 3, 5, 7],
2815
+ "data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.17, "cosStep": 0.09 }
2816
+ },
2817
+ "grid": {
2818
+ "dtype": "float16",
2819
+ "shape": [2, 3, 4, 7, 3],
2820
+ "data": { "kind": "linspace", "start": -3.5, "end": 3.5 }
2821
+ }
2822
+ },
2823
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 1, 3, 4, 7], "tolerance": 0.001, "relTolerance": 0.001 } }
2824
+ },
2825
+ {
2826
+ "name": "volume_scalar_linear_reflection_ac1_float16",
2827
+ "provenance": {
2828
+ "notes": "Scalar volumetric reflection with out-of-range taps; aligned cases include a dimension-one input to exercise the zero-width reflection interval."
2829
+ },
2830
+ "attrs": { "mode": "linear", "padding_mode": "reflection", "align_corners": 1 },
2831
+ "inputs": {
2832
+ "x": {
2833
+ "dtype": "float16",
2834
+ "shape": [2, 1, 1, 5, 7],
2835
+ "data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.17, "cosStep": 0.09 }
2836
+ },
2837
+ "grid": {
2838
+ "dtype": "float16",
2839
+ "shape": [2, 3, 4, 7, 3],
2840
+ "data": { "kind": "linspace", "start": -3.5, "end": 3.5 }
2841
+ }
2842
+ },
2843
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 1, 3, 4, 7], "tolerance": 0.001, "relTolerance": 0.001 } }
2844
+ },
2845
+ {
2846
+ "name": "volume_tail_linear_reflection_float16_per_voxel",
2847
+ "provenance": {
2848
+ "notes": "Explicit per-voxel comparison path retains the reflection behavior on incomplete channel vectors."
2849
+ },
2850
+ "attrs": { "mode": "linear", "padding_mode": "reflection", "align_corners": 0 },
2851
+ "inputs": {
2852
+ "x": {
2853
+ "dtype": "float16",
2854
+ "shape": [2, 3, 5, 7, 9],
2855
+ "data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.17, "cosStep": 0.09 }
2856
+ },
2857
+ "grid": {
2858
+ "dtype": "float16",
2859
+ "shape": [2, 7, 9, 11, 3],
2860
+ "data": { "kind": "linspace", "start": -3.5, "end": 3.5 }
2861
+ }
2862
+ },
2863
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 3, 7, 9, 11], "tolerance": 0.001, "relTolerance": 0.001 } },
2864
+ "tunables": { "RESOLVE_REFLECTION_TAPS": 0 }
2865
+ },
2866
+ {
2867
+ "name": "volume_scalar_cubic_reflection_ac0_float32",
2868
+ "provenance": {
2869
+ "notes": "Scalar volumetric reflection with out-of-range taps; aligned cases include a dimension-one input to exercise the zero-width reflection interval."
2870
+ },
2871
+ "attrs": { "mode": "cubic", "padding_mode": "reflection", "align_corners": 0 },
2872
+ "inputs": {
2873
+ "x": {
2874
+ "dtype": "float32",
2875
+ "shape": [2, 1, 3, 5, 7],
2876
+ "data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.17, "cosStep": 0.09 }
2877
+ },
2878
+ "grid": {
2879
+ "dtype": "float32",
2880
+ "shape": [2, 3, 4, 7, 3],
2881
+ "data": { "kind": "linspace", "start": -3.5, "end": 3.5 }
2882
+ }
2883
+ },
2884
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 1, 3, 4, 7], "tolerance": 0.0001, "relTolerance": 0.0001 } }
2885
+ },
2886
+ {
2887
+ "name": "volume_scalar_cubic_reflection_ac1_float32",
2888
+ "provenance": {
2889
+ "notes": "Scalar volumetric reflection with out-of-range taps; aligned cases include a dimension-one input to exercise the zero-width reflection interval."
2890
+ },
2891
+ "attrs": { "mode": "cubic", "padding_mode": "reflection", "align_corners": 1 },
2892
+ "inputs": {
2893
+ "x": {
2894
+ "dtype": "float32",
2895
+ "shape": [2, 1, 1, 5, 7],
2896
+ "data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.17, "cosStep": 0.09 }
2897
+ },
2898
+ "grid": {
2899
+ "dtype": "float32",
2900
+ "shape": [2, 3, 4, 7, 3],
2901
+ "data": { "kind": "linspace", "start": -3.5, "end": 3.5 }
2902
+ }
2903
+ },
2904
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 1, 3, 4, 7], "tolerance": 0.0001, "relTolerance": 0.0001 } }
2905
+ },
2906
+ {
2907
+ "name": "volume_tail_cubic_reflection_float32_per_voxel",
2908
+ "provenance": {
2909
+ "notes": "Explicit per-voxel comparison path retains the reflection behavior on incomplete channel vectors."
2910
+ },
2911
+ "attrs": { "mode": "cubic", "padding_mode": "reflection", "align_corners": 0 },
2912
+ "inputs": {
2913
+ "x": {
2914
+ "dtype": "float32",
2915
+ "shape": [2, 3, 5, 7, 9],
2916
+ "data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.17, "cosStep": 0.09 }
2917
+ },
2918
+ "grid": {
2919
+ "dtype": "float32",
2920
+ "shape": [2, 7, 9, 11, 3],
2921
+ "data": { "kind": "linspace", "start": -3.5, "end": 3.5 }
2922
+ }
2923
+ },
2924
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 7, 9, 11], "tolerance": 0.0001, "relTolerance": 0.0001 } },
2925
+ "tunables": { "RESOLVE_REFLECTION_TAPS": 0 }
2926
+ },
2927
+ {
2928
+ "name": "volume_scalar_cubic_reflection_ac0_float16",
2929
+ "provenance": {
2930
+ "notes": "Scalar volumetric reflection with out-of-range taps; aligned cases include a dimension-one input to exercise the zero-width reflection interval."
2931
+ },
2932
+ "attrs": { "mode": "cubic", "padding_mode": "reflection", "align_corners": 0 },
2933
+ "inputs": {
2934
+ "x": {
2935
+ "dtype": "float16",
2936
+ "shape": [2, 1, 3, 5, 7],
2937
+ "data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.17, "cosStep": 0.09 }
2938
+ },
2939
+ "grid": {
2940
+ "dtype": "float16",
2941
+ "shape": [2, 3, 4, 7, 3],
2942
+ "data": { "kind": "linspace", "start": -3.5, "end": 3.5 }
2943
+ }
2944
+ },
2945
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 1, 3, 4, 7], "tolerance": 0.001, "relTolerance": 0.001 } }
2946
+ },
2947
+ {
2948
+ "name": "volume_scalar_cubic_reflection_ac1_float16",
2949
+ "provenance": {
2950
+ "notes": "Scalar volumetric reflection with out-of-range taps; aligned cases include a dimension-one input to exercise the zero-width reflection interval."
2951
+ },
2952
+ "attrs": { "mode": "cubic", "padding_mode": "reflection", "align_corners": 1 },
2953
+ "inputs": {
2954
+ "x": {
2955
+ "dtype": "float16",
2956
+ "shape": [2, 1, 1, 5, 7],
2957
+ "data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.17, "cosStep": 0.09 }
2958
+ },
2959
+ "grid": {
2960
+ "dtype": "float16",
2961
+ "shape": [2, 3, 4, 7, 3],
2962
+ "data": { "kind": "linspace", "start": -3.5, "end": 3.5 }
2963
+ }
2964
+ },
2965
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 1, 3, 4, 7], "tolerance": 0.001, "relTolerance": 0.001 } }
2966
+ },
2967
+ {
2968
+ "name": "volume_tail_cubic_reflection_float16_per_voxel",
2969
+ "provenance": {
2970
+ "notes": "Explicit per-voxel comparison path retains the reflection behavior on incomplete channel vectors."
2971
+ },
2972
+ "attrs": { "mode": "cubic", "padding_mode": "reflection", "align_corners": 0 },
2973
+ "inputs": {
2974
+ "x": {
2975
+ "dtype": "float16",
2976
+ "shape": [2, 3, 5, 7, 9],
2977
+ "data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.17, "cosStep": 0.09 }
2978
+ },
2979
+ "grid": {
2980
+ "dtype": "float16",
2981
+ "shape": [2, 7, 9, 11, 3],
2982
+ "data": { "kind": "linspace", "start": -3.5, "end": 3.5 }
2983
+ }
2984
+ },
2985
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 3, 7, 9, 11], "tolerance": 0.001, "relTolerance": 0.001 } },
2986
+ "tunables": { "RESOLVE_REFLECTION_TAPS": 0 }
2987
+ },
2988
+ {
2989
+ "name": "reflection_coordinate_f32_rounding_rank4",
2990
+ "provenance": {
2991
+ "notes": "Float32 coordinate normalization outside the image must round before interpolation. Alternating pixels amplify the f32 versus f64 coordinate difference; expected value independently checked with ONNX Runtime's CPU provider."
2992
+ },
2993
+ "attrs": { "mode": "linear", "padding_mode": "reflection", "align_corners": 1 },
2994
+ "inputs": {
2995
+ "x": {
2996
+ "dtype": "float32",
2997
+ "shape": [1, 1, 256, 1],
2998
+ "data": {
2999
+ "kind": "values",
3000
+ "values": { "$ref": "#/fixtureArrays/reflection_coordinate_f32_rounding_rank4_input_x" }
3001
+ }
3002
+ },
3003
+ "grid": {
3004
+ "dtype": "float32",
3005
+ "shape": [1, 1, 1, 2],
3006
+ "data": { "kind": "values", "values": [0.0, 3.7643392086029053] }
3007
+ }
3008
+ },
3009
+ "outputs": {
3010
+ "y": {
3011
+ "dtype": "float32",
3012
+ "shape": [1, 1, 1, 1],
3013
+ "data": { "kind": "values", "values": [-0.0933837890625] },
3014
+ "tolerance": 0.0001,
3015
+ "relTolerance": 0.0001
3016
+ }
3017
+ }
3018
+ },
3019
+ {
3020
+ "name": "reflection_coordinate_f32_rounding_rank5",
3021
+ "provenance": {
3022
+ "notes": "Float32 coordinate normalization outside the image must round before interpolation. Alternating pixels amplify the f32 versus f64 coordinate difference; expected value independently checked with ONNX Runtime's CPU provider."
3023
+ },
3024
+ "attrs": { "mode": "linear", "padding_mode": "reflection", "align_corners": 1 },
3025
+ "inputs": {
3026
+ "x": {
3027
+ "dtype": "float32",
3028
+ "shape": [1, 1, 1, 256, 1],
3029
+ "data": {
3030
+ "kind": "values",
3031
+ "values": { "$ref": "#/fixtureArrays/reflection_coordinate_f32_rounding_rank4_input_x" }
3032
+ }
3033
+ },
3034
+ "grid": {
3035
+ "dtype": "float32",
3036
+ "shape": [1, 1, 1, 1, 3],
3037
+ "data": { "kind": "values", "values": [0.0, 3.7643392086029053, 0.0] }
3038
+ }
3039
+ },
3040
+ "outputs": {
3041
+ "y": {
3042
+ "dtype": "float32",
3043
+ "shape": [1, 1, 1, 1, 1],
3044
+ "data": { "kind": "values", "values": [-0.0933837890625] },
3045
+ "tolerance": 0.0001,
3046
+ "relTolerance": 0.0001
3047
+ }
3048
+ }
3049
  }
3050
  ]
3051
  }