sync 91d990483a17
Browse files- README.md +16 -11
- build/webgpu/batch-normalization-nc-vec4.wgsl.jinja +19 -16
- build/webgpu/batch-normalization-nchw-flat-vec4.wgsl.jinja +63 -0
- build/webgpu/batch-normalization-nchw-vec4.wgsl.jinja +15 -15
- build/webgpu/batch-normalization-nchw.wgsl.jinja +15 -18
- build/webgpu/bench.json +28 -1
- build/webgpu/manifest.json +114 -200
- build/webgpu/metadata.json +18 -9
- build/webgpu/test.json +66 -4
README.md
CHANGED
|
@@ -18,19 +18,19 @@ See the [ONNX `BatchNormalization` spec](https://onnx.ai/onnx/operators/onnx__Ba
|
|
| 18 |
|
| 19 |
## Inputs
|
| 20 |
|
| 21 |
-
| Name |
|
| 22 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
-
| `
|
| 24 |
-
| `scale` |
|
| 25 |
-
| `
|
| 26 |
-
| `
|
| 27 |
-
| `
|
| 28 |
|
| 29 |
## Outputs
|
| 30 |
|
| 31 |
-
| Name |
|
| 32 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 33 |
-
| `
|
| 34 |
|
| 35 |
## Attributes
|
| 36 |
|
|
@@ -50,20 +50,25 @@ Default values (overridable per request):
|
|
| 50 |
|
| 51 |
## Files
|
| 52 |
|
| 53 |
-
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
|
| 54 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 55 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 56 |
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
| 57 |
- [`batch-normalization-nc-vec4.wgsl.jinja`](build/webgpu/batch-normalization-nc-vec4.wgsl.jinja)
|
|
|
|
| 58 |
- [`batch-normalization-nchw-vec4.wgsl.jinja`](build/webgpu/batch-normalization-nchw-vec4.wgsl.jinja)
|
| 59 |
- [`batch-normalization-nchw.wgsl.jinja`](build/webgpu/batch-normalization-nchw.wgsl.jinja)
|
| 60 |
|
| 61 |
## Use with `@huggingface/kernels`
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
|
|
|
| 67 |
|
| 68 |
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 69 |
|
|
|
|
| 18 |
|
| 19 |
## Inputs
|
| 20 |
|
| 21 |
+
| Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
|
| 22 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `x` | `X` | `T` | — | — | Input data tensor with shape `(N, C, D1, ..., Dn)`, normalized independently per channel using the supplied estimated statistics. | required |
|
| 24 |
+
| `scale` | — | `T` | `1` | — | Per-channel scale tensor with shape `(C)`. | required |
|
| 25 |
+
| `b` | `B` | `T` | `1` | — | Per-channel bias tensor with shape `(C)`. | required |
|
| 26 |
+
| `inputMean` | `input_mean` | `T` | `1` | — | Precomputed estimated mean tensor with shape `(C)` used for inference. | required |
|
| 27 |
+
| `inputVar` | `input_var` | `T` | `1` | — | Precomputed estimated variance tensor with shape `(C)` used for inference. | required |
|
| 28 |
|
| 29 |
## Outputs
|
| 30 |
|
| 31 |
+
| Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
|
| 32 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 33 |
+
| `y` | `Y` | `T` | same as `x` | same as `x` | Batch-normalized output tensor with the same shape as `X`. | required |
|
| 34 |
|
| 35 |
## Attributes
|
| 36 |
|
|
|
|
| 50 |
|
| 51 |
## Files
|
| 52 |
|
| 53 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
|
| 54 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 55 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 56 |
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
| 57 |
- [`batch-normalization-nc-vec4.wgsl.jinja`](build/webgpu/batch-normalization-nc-vec4.wgsl.jinja)
|
| 58 |
+
- [`batch-normalization-nchw-flat-vec4.wgsl.jinja`](build/webgpu/batch-normalization-nchw-flat-vec4.wgsl.jinja)
|
| 59 |
- [`batch-normalization-nchw-vec4.wgsl.jinja`](build/webgpu/batch-normalization-nchw-vec4.wgsl.jinja)
|
| 60 |
- [`batch-normalization-nchw.wgsl.jinja`](build/webgpu/batch-normalization-nchw.wgsl.jinja)
|
| 61 |
|
| 62 |
## Use with `@huggingface/kernels`
|
| 63 |
|
| 64 |
+
```sh
|
| 65 |
+
npm install --save-exact @huggingface/kernels@0.0.1-preview.2
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
|
| 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/batch-normalization-nc-vec4.wgsl.jinja
CHANGED
|
@@ -1,30 +1,30 @@
|
|
| 1 |
{% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
|
| 2 |
{% if note == "dispatch-limit" %}
|
| 3 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 4 |
-
//
|
| 5 |
{% elif note == "limit" %}
|
| 6 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 7 |
-
//
|
| 8 |
{% elif note == "device-axis" %}
|
| 9 |
-
// The flat dispatch is folded across x/y at
|
| 10 |
-
//
|
| 11 |
{% elif note == "vec4-limit" %}
|
| 12 |
-
// 2D-folded flat vec4 index: gid.y carries the high bits past the
|
| 13 |
-
//
|
| 14 |
{% elif note == "element-limit" %}
|
| 15 |
// 2D-folded flat element index: gid.y carries the high bits past the
|
| 16 |
-
//
|
| 17 |
{% elif note == "dispatch" %}
|
| 18 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 19 |
-
//
|
| 20 |
{% endif %}
|
| 21 |
{% if bound == "" %}
|
| 22 |
-
let {{ name }} = gid.x + gid.y *
|
| 23 |
{%- elif guardInline %}
|
| 24 |
-
let {{ name }} = gid.x + gid.y *
|
| 25 |
if ({{ name }} >= {{ bound }}) { return; }
|
| 26 |
{%- else %}
|
| 27 |
-
let {{ name }} = gid.x + gid.y *
|
| 28 |
if ({{ name }} >= {{ bound }}) {
|
| 29 |
return;
|
| 30 |
}
|
|
@@ -36,9 +36,12 @@
|
|
| 36 |
// Rank-2 [N, C] inference vec4 specialization. Vectors run across adjacent channels, so
|
| 37 |
// scale/bias/mean/var are also bound as vec4<f32> and C must be divisible by 4.
|
| 38 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 39 |
-
fn main(@builtin(global_invocation_id) gid: vec3<u32>
|
| 40 |
{{ flat_index_2d("i", "params.count4") }}
|
| 41 |
let channel4 = i % params.channels4;
|
| 42 |
let alpha = scale[channel4] * inverseSqrt(input_var[channel4] + vec4<f32>(params.epsilon));
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
| 44 |
}
|
|
|
|
| 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 |
}
|
|
|
|
| 36 |
// Rank-2 [N, C] inference vec4 specialization. Vectors run across adjacent channels, so
|
| 37 |
// scale/bias/mean/var are also bound as vec4<f32> and C must be divisible by 4.
|
| 38 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 39 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 40 |
{{ flat_index_2d("i", "params.count4") }}
|
| 41 |
let channel4 = i % params.channels4;
|
| 42 |
let alpha = scale[channel4] * inverseSqrt(input_var[channel4] + vec4<f32>(params.epsilon));
|
| 43 |
+
// Subtract the mean before scaling. The expanded form
|
| 44 |
+
// x * alpha + (bias - mean * alpha) can lose the small residual (x - mean)
|
| 45 |
+
// when |mean| is much larger than |x - mean|.
|
| 46 |
+
y[i] = (x[i] - input_mean[channel4]) * alpha + bias[channel4];
|
| 47 |
}
|
build/webgpu/batch-normalization-nchw-flat-vec4.wgsl.jinja
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
// Inference-only vec4 specialization for channel planes whose length is not a
|
| 37 |
+
// multiple of four. The vec4 grid covers the flat tensor, so a group can cross a
|
| 38 |
+
// channel boundary. Boundary groups resolve each component's channel separately;
|
| 39 |
+
// all other groups share one channel. Each component computes:
|
| 40 |
+
// (x - mean) * inverseSqrt(var + epsilon) * scale + bias
|
| 41 |
+
fn normalize_at(value: f32, channel: u32) -> f32 {
|
| 42 |
+
let normalized = (value - input_mean[channel]) * inverseSqrt(input_var[channel] + params.epsilon);
|
| 43 |
+
return normalized * scale[channel] + bias[channel];
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 47 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 48 |
+
{{ flat_index_2d("i", "params.count4") }}
|
| 49 |
+
let base = i * 4u;
|
| 50 |
+
let first = (base / params.spatial) % params.channels;
|
| 51 |
+
let last = ((base + 3u) / params.spatial) % params.channels;
|
| 52 |
+
let value = x[i];
|
| 53 |
+
if (first == last) {
|
| 54 |
+
let normalized = (value - vec4<f32>(input_mean[first])) * inverseSqrt(input_var[first] + params.epsilon);
|
| 55 |
+
y[i] = normalized * vec4<f32>(scale[first]) + vec4<f32>(bias[first]);
|
| 56 |
+
return;
|
| 57 |
+
}
|
| 58 |
+
y[i] = vec4<f32>(
|
| 59 |
+
normalize_at(value.x, first),
|
| 60 |
+
normalize_at(value.y, ((base + 1u) / params.spatial) % params.channels),
|
| 61 |
+
normalize_at(value.z, ((base + 2u) / params.spatial) % params.channels),
|
| 62 |
+
normalize_at(value.w, last));
|
| 63 |
+
}
|
build/webgpu/batch-normalization-nchw-vec4.wgsl.jinja
CHANGED
|
@@ -1,30 +1,30 @@
|
|
| 1 |
{% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
|
| 2 |
{% if note == "dispatch-limit" %}
|
| 3 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 4 |
-
//
|
| 5 |
{% elif note == "limit" %}
|
| 6 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 7 |
-
//
|
| 8 |
{% elif note == "device-axis" %}
|
| 9 |
-
// The flat dispatch is folded across x/y at
|
| 10 |
-
//
|
| 11 |
{% elif note == "vec4-limit" %}
|
| 12 |
-
// 2D-folded flat vec4 index: gid.y carries the high bits past the
|
| 13 |
-
//
|
| 14 |
{% elif note == "element-limit" %}
|
| 15 |
// 2D-folded flat element index: gid.y carries the high bits past the
|
| 16 |
-
//
|
| 17 |
{% elif note == "dispatch" %}
|
| 18 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 19 |
-
//
|
| 20 |
{% endif %}
|
| 21 |
{% if bound == "" %}
|
| 22 |
-
let {{ name }} = gid.x + gid.y *
|
| 23 |
{%- elif guardInline %}
|
| 24 |
-
let {{ name }} = gid.x + gid.y *
|
| 25 |
if ({{ name }} >= {{ bound }}) { return; }
|
| 26 |
{%- else %}
|
| 27 |
-
let {{ name }} = gid.x + gid.y *
|
| 28 |
if ({{ name }} >= {{ bound }}) {
|
| 29 |
return;
|
| 30 |
}
|
|
@@ -38,7 +38,7 @@
|
|
| 38 |
// calculation is:
|
| 39 |
// (x - mean) * inverseSqrt(var + epsilon) * scale + bias
|
| 40 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 41 |
-
fn main(@builtin(global_invocation_id) gid: vec3<u32>
|
| 42 |
{{ flat_index_2d("i", "params.count4") }}
|
| 43 |
let channel = (i / params.spatial4) % params.channels;
|
| 44 |
let normalized = (x[i] - vec4<f32>(input_mean[channel])) * inverseSqrt(input_var[channel] + params.epsilon);
|
|
|
|
| 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 |
}
|
|
|
|
| 38 |
// calculation is:
|
| 39 |
// (x - mean) * inverseSqrt(var + epsilon) * scale + bias
|
| 40 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 41 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 42 |
{{ flat_index_2d("i", "params.count4") }}
|
| 43 |
let channel = (i / params.spatial4) % params.channels;
|
| 44 |
let normalized = (x[i] - vec4<f32>(input_mean[channel])) * inverseSqrt(input_var[channel] + params.epsilon);
|
build/webgpu/batch-normalization-nchw.wgsl.jinja
CHANGED
|
@@ -1,42 +1,39 @@
|
|
| 1 |
{% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
|
| 2 |
{% if note == "dispatch-limit" %}
|
| 3 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 4 |
-
//
|
| 5 |
{% elif note == "limit" %}
|
| 6 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 7 |
-
//
|
| 8 |
{% elif note == "device-axis" %}
|
| 9 |
-
// The flat dispatch is folded across x/y at
|
| 10 |
-
//
|
| 11 |
{% elif note == "vec4-limit" %}
|
| 12 |
-
// 2D-folded flat vec4 index: gid.y carries the high bits past the
|
| 13 |
-
//
|
| 14 |
{% elif note == "element-limit" %}
|
| 15 |
// 2D-folded flat element index: gid.y carries the high bits past the
|
| 16 |
-
//
|
| 17 |
{% elif note == "dispatch" %}
|
| 18 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 19 |
-
//
|
| 20 |
{% endif %}
|
| 21 |
{% if bound == "" %}
|
| 22 |
-
let {{ name }} = gid.x + gid.y *
|
| 23 |
{%- elif guardInline %}
|
| 24 |
-
let {{ name }} = gid.x + gid.y *
|
| 25 |
if ({{ name }} >= {{ bound }}) { return; }
|
| 26 |
{%- else %}
|
| 27 |
-
let {{ name }} = gid.x + gid.y *
|
| 28 |
if ({{ name }} >= {{ bound }}) {
|
| 29 |
return;
|
| 30 |
}
|
| 31 |
{%- endif %}
|
| 32 |
{% endmacro %}
|
| 33 |
|
| 34 |
-
{% if usesF16 %}
|
| 35 |
-
enable f16;
|
| 36 |
-
{% endif %}
|
| 37 |
{{ env.wgsl.resourceDeclarations }}
|
| 38 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 39 |
-
fn main(@builtin(global_invocation_id) gid: vec3<u32>
|
| 40 |
{{ flat_index_2d("index") }}
|
| 41 |
let spatial = params.height * params.width;
|
| 42 |
let channel = (index / spatial) % params.channels;
|
|
|
|
| 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 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 36 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 37 |
{{ flat_index_2d("index") }}
|
| 38 |
let spatial = params.height * params.width;
|
| 39 |
let channel = (index / spatial) % params.channels;
|
build/webgpu/bench.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
{
|
| 2 |
-
"op": "ai.onnx.BatchNormalization",
|
| 3 |
"cases": [
|
| 4 |
{
|
| 5 |
"name": "nchw_4x64x112x112_vec4",
|
|
@@ -84,6 +83,34 @@
|
|
| 84 |
}
|
| 85 |
]
|
| 86 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
}
|
| 88 |
]
|
| 89 |
}
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"cases": [
|
| 3 |
{
|
| 4 |
"name": "nchw_4x64x112x112_vec4",
|
|
|
|
| 83 |
}
|
| 84 |
]
|
| 85 |
}
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"name": "batchnorm-nchw-f32-c256-plane49-unaligned",
|
| 89 |
+
"preset": "edge",
|
| 90 |
+
"attrs": { "epsilon": 0.00001 },
|
| 91 |
+
"inputs": {
|
| 92 |
+
"x": { "dtype": "float32", "shape": [128, 256, 7, 7] },
|
| 93 |
+
"scale": { "dtype": "float32", "shape": [256] },
|
| 94 |
+
"b": { "dtype": "float32", "shape": [256] },
|
| 95 |
+
"inputMean": { "dtype": "float32", "shape": [256] },
|
| 96 |
+
"inputVar": { "dtype": "float32", "shape": [256], "min": 0.5, "max": 2 }
|
| 97 |
+
},
|
| 98 |
+
"outputs": { "y": { "dtype": "float32", "shape": [128, 256, 7, 7] } },
|
| 99 |
+
"bench": { "metrics": [{ "type": "bandwidth", "value": "numel(shapes.x) * 4 * 2" }] }
|
| 100 |
+
},
|
| 101 |
+
{
|
| 102 |
+
"name": "batchnorm-nchw-f32-c256-plane64-aligned-control",
|
| 103 |
+
"preset": "edge",
|
| 104 |
+
"attrs": { "epsilon": 0.00001 },
|
| 105 |
+
"inputs": {
|
| 106 |
+
"x": { "dtype": "float32", "shape": [98, 256, 8, 8] },
|
| 107 |
+
"scale": { "dtype": "float32", "shape": [256] },
|
| 108 |
+
"b": { "dtype": "float32", "shape": [256] },
|
| 109 |
+
"inputMean": { "dtype": "float32", "shape": [256] },
|
| 110 |
+
"inputVar": { "dtype": "float32", "shape": [256], "min": 0.5, "max": 2 }
|
| 111 |
+
},
|
| 112 |
+
"outputs": { "y": { "dtype": "float32", "shape": [98, 256, 8, 8] } },
|
| 113 |
+
"bench": { "metrics": [{ "type": "bandwidth", "value": "numel(shapes.x) * 4 * 2" }] }
|
| 114 |
}
|
| 115 |
]
|
| 116 |
}
|
build/webgpu/manifest.json
CHANGED
|
@@ -2,241 +2,155 @@
|
|
| 2 |
"domain": "ai.onnx",
|
| 3 |
"name": "BatchNormalization",
|
| 4 |
"sinceVersion": 15,
|
| 5 |
-
"
|
| 6 |
-
|
| 7 |
-
{
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
},
|
| 12 |
-
{ "role": "scale", "dtype": "T", "rank": 1, "description": "Per-channel scale tensor with shape `(C)`." },
|
| 13 |
-
{ "role": "B", "dtype": "T", "rank": 1, "description": "Per-channel bias tensor with shape `(C)`." },
|
| 14 |
-
{
|
| 15 |
-
"role": "input_mean",
|
| 16 |
-
"dtype": "T",
|
| 17 |
-
"rank": 1,
|
| 18 |
-
"description": "Precomputed estimated mean tensor with shape `(C)` used for inference."
|
| 19 |
-
},
|
| 20 |
-
{
|
| 21 |
-
"role": "input_var",
|
| 22 |
-
"dtype": "T",
|
| 23 |
-
"rank": 1,
|
| 24 |
-
"description": "Precomputed estimated variance tensor with shape `(C)` used for inference."
|
| 25 |
-
}
|
| 26 |
-
],
|
| 27 |
-
"outputs": [
|
| 28 |
-
{
|
| 29 |
-
"role": "Y",
|
| 30 |
-
"dtype": "T",
|
| 31 |
-
"rank": "ranks.X",
|
| 32 |
-
"description": "Batch-normalized output tensor with the same shape as `X`.",
|
| 33 |
-
"shape": "shapes.X"
|
| 34 |
-
}
|
| 35 |
-
],
|
| 36 |
-
"attributes": { "epsilon": 0.00001, "momentum": 0.9, "training_mode": 0 },
|
| 37 |
-
"attributeDescriptions": {
|
| 38 |
-
"epsilon": "Small value added to the variance before taking the square root to avoid division by zero.",
|
| 39 |
-
"momentum": "Standard ONNX running-statistics momentum. This inference-only package accepts the default `0.9`; non-default values are reserved for the unsupported training-state update.",
|
| 40 |
-
"training_mode": "Execution mode. This inference-only package supports the default value 0; value 1 is rejected because the ONNX training outputs are not exposed."
|
| 41 |
-
},
|
| 42 |
-
"attributeConstraints": {
|
| 43 |
-
"momentum": { "values": [0.9], "comparison": "float32" },
|
| 44 |
-
"training_mode": { "values": [0] }
|
| 45 |
},
|
|
|
|
|
|
|
|
|
|
| 46 |
"typeConstraints": { "T": ["float32", "float16"] },
|
| 47 |
-
"
|
| 48 |
-
"x": { "kind": "tensor", "semantic": "X", "role": "input" },
|
| 49 |
-
"scale": { "kind": "tensor", "semantic": "scale", "role": "input" },
|
| 50 |
-
"b": { "kind": "tensor", "semantic": "B", "role": "input" },
|
| 51 |
-
"inputMean": { "kind": "tensor", "semantic": "input_mean", "role": "input" },
|
| 52 |
-
"inputVar": { "kind": "tensor", "semantic": "input_var", "role": "input" },
|
| 53 |
-
"y": { "kind": "tensor", "semantic": "Y", "role": "output" }
|
| 54 |
-
},
|
| 55 |
-
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 56 |
"derive": {
|
| 57 |
-
"normalizationParamsOk": "ranks.scale == 1 and ranks.
|
| 58 |
-
"inferenceContractOk": "f16Ok(dtypes.T) and ranks.
|
| 59 |
},
|
| 60 |
-
"
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
"
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
"name": "
|
| 92 |
-
"
|
| 93 |
-
"
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
"
|
| 101 |
-
"
|
| 102 |
-
"
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
{ "name": "channels4", "type": "u32", "value": "dim(shapes.X, 1) / 4" },
|
| 107 |
-
{ "name": "epsilon", "type": "f32", "value": "attrs.epsilon" }
|
| 108 |
-
]
|
| 109 |
-
}
|
| 110 |
-
}
|
| 111 |
-
],
|
| 112 |
-
"spatialInferenceVec4": [
|
| 113 |
-
{
|
| 114 |
-
"name": "x",
|
| 115 |
-
"arg": "x",
|
| 116 |
-
"semantic": "X",
|
| 117 |
-
"buffer": { "type": "read-only-storage" },
|
| 118 |
-
"elementType": "vec4<f32>"
|
| 119 |
-
},
|
| 120 |
-
{
|
| 121 |
-
"name": "scale",
|
| 122 |
-
"arg": "scale",
|
| 123 |
-
"semantic": "scale",
|
| 124 |
-
"buffer": { "type": "read-only-storage" },
|
| 125 |
-
"elementType": "$T"
|
| 126 |
-
},
|
| 127 |
-
{ "name": "bias", "arg": "b", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
|
| 128 |
-
{
|
| 129 |
-
"name": "input_mean",
|
| 130 |
-
"arg": "inputMean",
|
| 131 |
-
"semantic": "input_mean",
|
| 132 |
-
"buffer": { "type": "read-only-storage" },
|
| 133 |
-
"elementType": "$T"
|
| 134 |
-
},
|
| 135 |
-
{
|
| 136 |
-
"name": "input_var",
|
| 137 |
-
"arg": "inputVar",
|
| 138 |
-
"semantic": "input_var",
|
| 139 |
-
"buffer": { "type": "read-only-storage" },
|
| 140 |
-
"elementType": "$T"
|
| 141 |
-
},
|
| 142 |
-
{ "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "vec4<f32>" },
|
| 143 |
-
{
|
| 144 |
-
"name": "params",
|
| 145 |
-
"semantic": "kernel.params",
|
| 146 |
-
"buffer": { "type": "uniform" },
|
| 147 |
-
"struct": {
|
| 148 |
-
"name": "Params",
|
| 149 |
-
"fields": [
|
| 150 |
-
{ "name": "count4", "type": "u32", "value": "numel(shapes.Y) / 4" },
|
| 151 |
-
{ "name": "spatial4", "type": "u32", "value": "inner(shapes.X, 1) / 4" },
|
| 152 |
-
{ "name": "channels", "type": "u32", "value": "dim(shapes.X, 1)" },
|
| 153 |
-
{ "name": "epsilon", "type": "f32", "value": "attrs.epsilon" }
|
| 154 |
-
]
|
| 155 |
-
}
|
| 156 |
-
}
|
| 157 |
-
],
|
| 158 |
-
"inferenceScalar": [
|
| 159 |
-
{ "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
|
| 160 |
-
{
|
| 161 |
-
"name": "scale",
|
| 162 |
-
"arg": "scale",
|
| 163 |
-
"semantic": "scale",
|
| 164 |
-
"buffer": { "type": "read-only-storage" },
|
| 165 |
-
"elementType": "$T"
|
| 166 |
-
},
|
| 167 |
-
{ "name": "bias", "arg": "b", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
|
| 168 |
-
{
|
| 169 |
-
"name": "input_mean",
|
| 170 |
-
"arg": "inputMean",
|
| 171 |
-
"semantic": "input_mean",
|
| 172 |
-
"buffer": { "type": "read-only-storage" },
|
| 173 |
-
"elementType": "$T"
|
| 174 |
-
},
|
| 175 |
-
{
|
| 176 |
-
"name": "input_var",
|
| 177 |
-
"arg": "inputVar",
|
| 178 |
-
"semantic": "input_var",
|
| 179 |
-
"buffer": { "type": "read-only-storage" },
|
| 180 |
-
"elementType": "$T"
|
| 181 |
-
},
|
| 182 |
-
{ "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
|
| 183 |
-
{
|
| 184 |
-
"name": "params",
|
| 185 |
-
"semantic": "kernel.params",
|
| 186 |
-
"buffer": { "type": "uniform" },
|
| 187 |
-
"struct": {
|
| 188 |
-
"name": "Params",
|
| 189 |
-
"fields": [
|
| 190 |
-
{ "name": "channels", "type": "u32", "value": "dim(shapes.X, 1)" },
|
| 191 |
-
{ "name": "height", "type": "u32", "value": "1 if ranks.X == 2 else dim(shapes.X, 2)" },
|
| 192 |
-
{ "name": "width", "type": "u32", "value": "1 if ranks.X == 2 else inner(shapes.X, 2)" },
|
| 193 |
-
{ "name": "epsilon", "type": "f32", "value": "attrs.epsilon" },
|
| 194 |
-
{ "name": "count", "type": "u32", "value": "numel(shapes.Y)" }
|
| 195 |
-
]
|
| 196 |
-
}
|
| 197 |
-
}
|
| 198 |
-
]
|
| 199 |
},
|
| 200 |
"variants": [
|
| 201 |
{
|
| 202 |
"id": "inference_scalar",
|
| 203 |
-
"
|
| 204 |
-
"constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
|
| 205 |
"passes": [
|
| 206 |
{
|
| 207 |
"id": "main",
|
| 208 |
"name": "BatchNormalization.InferenceScalar",
|
| 209 |
"shader": "batch-normalization-nchw.wgsl.jinja",
|
| 210 |
-
"bindings":
|
| 211 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
}
|
| 213 |
]
|
| 214 |
},
|
| 215 |
{
|
| 216 |
"id": "nc_inference_vec4",
|
| 217 |
"priority": 110,
|
| 218 |
-
"when": ["
|
| 219 |
"passes": [
|
| 220 |
{
|
| 221 |
"id": "main",
|
| 222 |
"name": "BatchNormalization.NcInferenceVec4",
|
| 223 |
"shader": "batch-normalization-nc-vec4.wgsl.jinja",
|
| 224 |
-
"bindings": "
|
| 225 |
-
"dispatch": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
}
|
| 227 |
]
|
| 228 |
},
|
| 229 |
{
|
| 230 |
"id": "nchw_inference_vec4",
|
| 231 |
"priority": 100,
|
| 232 |
-
"when": ["
|
| 233 |
"passes": [
|
| 234 |
{
|
| 235 |
"id": "main",
|
| 236 |
"name": "BatchNormalization.InferenceVec4",
|
| 237 |
"shader": "batch-normalization-nchw-vec4.wgsl.jinja",
|
| 238 |
-
"bindings": "
|
| 239 |
-
"dispatch": {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
}
|
| 241 |
]
|
| 242 |
}
|
|
|
|
| 2 |
"domain": "ai.onnx",
|
| 3 |
"name": "BatchNormalization",
|
| 4 |
"sinceVersion": 15,
|
| 5 |
+
"inputs": {
|
| 6 |
+
"x": { "onnx": "X", "dtype": "T" },
|
| 7 |
+
"scale": { "dtype": "T", "rank": 1 },
|
| 8 |
+
"b": { "onnx": "B", "dtype": "T", "rank": 1 },
|
| 9 |
+
"inputMean": { "onnx": "input_mean", "dtype": "T", "rank": 1 },
|
| 10 |
+
"inputVar": { "onnx": "input_var", "dtype": "T", "rank": 1 }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
},
|
| 12 |
+
"outputs": { "y": { "onnx": "Y", "dtype": "T", "rank": "ranks.x", "shape": "shapes.x" } },
|
| 13 |
+
"attributes": { "epsilon": { "default": 0.00001 }, "momentum": { "default": 0.9 }, "training_mode": { "default": 0 } },
|
| 14 |
+
"attributeConstraints": { "momentum": { "values": [0.9] }, "training_mode": { "values": [0] } },
|
| 15 |
"typeConstraints": { "T": ["float32", "float16"] },
|
| 16 |
+
"tunables": { "WORKGROUP_SIZE": { "default": 256 } },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
"derive": {
|
| 18 |
+
"normalizationParamsOk": "ranks.scale == 1 and ranks.b == 1 and ranks.inputMean == 1 and ranks.inputVar == 1 and dim(shapes.scale, 0) == dim(shapes.x, 1) and dim(shapes.b, 0) == dim(shapes.x, 1) and dim(shapes.inputMean, 0) == dim(shapes.x, 1) and dim(shapes.inputVar, 0) == dim(shapes.x, 1)",
|
| 19 |
+
"inferenceContractOk": "f16Ok(dtypes.T) and ranks.x >= 2 and ranks.y == ranks.x and sameShape(shapes.y, shapes.x) and normalizationParamsOk"
|
| 20 |
},
|
| 21 |
+
"when": ["inferenceContractOk"],
|
| 22 |
+
"bindings": {
|
| 23 |
+
"scale": { "buffer": "read-only-storage", "elementType": "$T" },
|
| 24 |
+
"bias": { "arg": "b", "buffer": "read-only-storage", "elementType": "$T" },
|
| 25 |
+
"input_mean": { "arg": "inputMean", "buffer": "read-only-storage", "elementType": "$T" },
|
| 26 |
+
"input_var": { "arg": "inputVar", "buffer": "read-only-storage", "elementType": "$T" },
|
| 27 |
+
"x_2": { "name": "x", "buffer": "read-only-storage", "elementType": "vec4<f32>" },
|
| 28 |
+
"scale_2": { "name": "scale", "buffer": "read-only-storage", "elementType": "vec4<f32>" },
|
| 29 |
+
"bias_2": { "arg": "b", "name": "bias", "buffer": "read-only-storage", "elementType": "vec4<f32>" },
|
| 30 |
+
"input_mean_2": {
|
| 31 |
+
"arg": "inputMean",
|
| 32 |
+
"name": "input_mean",
|
| 33 |
+
"buffer": "read-only-storage",
|
| 34 |
+
"elementType": "vec4<f32>"
|
| 35 |
+
},
|
| 36 |
+
"input_var_2": { "arg": "inputVar", "name": "input_var", "buffer": "read-only-storage", "elementType": "vec4<f32>" },
|
| 37 |
+
"y_2": { "name": "y", "buffer": "storage", "elementType": "vec4<f32>" },
|
| 38 |
+
"params_2": {
|
| 39 |
+
"name": "params",
|
| 40 |
+
"buffer": "uniform",
|
| 41 |
+
"struct": [
|
| 42 |
+
{ "name": "count4", "type": "u32", "value": "numel(shapes.y) / 4" },
|
| 43 |
+
{ "name": "channels4", "type": "u32", "value": "dim(shapes.x, 1) / 4" },
|
| 44 |
+
{ "name": "epsilon", "type": "f32", "value": "attrs.epsilon" }
|
| 45 |
+
]
|
| 46 |
+
},
|
| 47 |
+
"params_3": {
|
| 48 |
+
"name": "params",
|
| 49 |
+
"buffer": "uniform",
|
| 50 |
+
"struct": [
|
| 51 |
+
{ "name": "count4", "type": "u32", "value": "numel(shapes.y) / 4" },
|
| 52 |
+
{ "name": "spatial", "type": "u32", "value": "inner(shapes.x, 1)" },
|
| 53 |
+
{ "name": "channels", "type": "u32", "value": "dim(shapes.x, 1)" },
|
| 54 |
+
{ "name": "epsilon", "type": "f32", "value": "attrs.epsilon" }
|
| 55 |
+
]
|
| 56 |
+
},
|
| 57 |
+
"params_4": {
|
| 58 |
+
"name": "params",
|
| 59 |
+
"buffer": "uniform",
|
| 60 |
+
"struct": [
|
| 61 |
+
{ "name": "count4", "type": "u32", "value": "numel(shapes.y) / 4" },
|
| 62 |
+
{ "name": "spatial4", "type": "u32", "value": "inner(shapes.x, 1) / 4" },
|
| 63 |
+
{ "name": "channels", "type": "u32", "value": "dim(shapes.x, 1)" },
|
| 64 |
+
{ "name": "epsilon", "type": "f32", "value": "attrs.epsilon" }
|
| 65 |
+
]
|
| 66 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
},
|
| 68 |
"variants": [
|
| 69 |
{
|
| 70 |
"id": "inference_scalar",
|
| 71 |
+
"derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
|
|
|
|
| 72 |
"passes": [
|
| 73 |
{
|
| 74 |
"id": "main",
|
| 75 |
"name": "BatchNormalization.InferenceScalar",
|
| 76 |
"shader": "batch-normalization-nchw.wgsl.jinja",
|
| 77 |
+
"bindings": [
|
| 78 |
+
"x",
|
| 79 |
+
"scale",
|
| 80 |
+
"bias",
|
| 81 |
+
"input_mean",
|
| 82 |
+
"input_var",
|
| 83 |
+
"y",
|
| 84 |
+
{
|
| 85 |
+
"name": "params",
|
| 86 |
+
"struct": [
|
| 87 |
+
{ "name": "channels", "type": "u32", "value": "dim(shapes.x, 1)" },
|
| 88 |
+
{ "name": "height", "type": "u32", "value": "1 if ranks.x == 2 else dim(shapes.x, 2)" },
|
| 89 |
+
{ "name": "width", "type": "u32", "value": "1 if ranks.x == 2 else inner(shapes.x, 2)" },
|
| 90 |
+
{ "name": "epsilon", "type": "f32", "value": "attrs.epsilon" },
|
| 91 |
+
{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }
|
| 92 |
+
]
|
| 93 |
+
}
|
| 94 |
+
],
|
| 95 |
+
"dispatch": {
|
| 96 |
+
"x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 97 |
+
"y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 98 |
+
"z": 1
|
| 99 |
+
}
|
| 100 |
}
|
| 101 |
]
|
| 102 |
},
|
| 103 |
{
|
| 104 |
"id": "nc_inference_vec4",
|
| 105 |
"priority": 110,
|
| 106 |
+
"when": ["dtypes.T == \"f32\"", "ranks.x == 2", "dim(shapes.x, 1) % 4 == 0"],
|
| 107 |
"passes": [
|
| 108 |
{
|
| 109 |
"id": "main",
|
| 110 |
"name": "BatchNormalization.NcInferenceVec4",
|
| 111 |
"shader": "batch-normalization-nc-vec4.wgsl.jinja",
|
| 112 |
+
"bindings": ["x_2", "scale_2", "bias_2", "input_mean_2", "input_var_2", "y_2", "params_2"],
|
| 113 |
+
"dispatch": {
|
| 114 |
+
"x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 115 |
+
"y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 116 |
+
"z": 1
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
]
|
| 120 |
+
},
|
| 121 |
+
{
|
| 122 |
+
"id": "nchw_inference_flat_vec4",
|
| 123 |
+
"priority": 90,
|
| 124 |
+
"when": ["dtypes.T == \"f32\"", "ranks.x >= 3", "inner(shapes.x, 1) % 4 != 0 and numel(shapes.x) % 4 == 0"],
|
| 125 |
+
"passes": [
|
| 126 |
+
{
|
| 127 |
+
"id": "main",
|
| 128 |
+
"name": "BatchNormalization.InferenceFlatVec4",
|
| 129 |
+
"shader": "batch-normalization-nchw-flat-vec4.wgsl.jinja",
|
| 130 |
+
"bindings": ["x_2", "scale", "bias", "input_mean", "input_var", "y_2", "params_3"],
|
| 131 |
+
"dispatch": {
|
| 132 |
+
"x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 133 |
+
"y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 134 |
+
"z": 1
|
| 135 |
+
}
|
| 136 |
}
|
| 137 |
]
|
| 138 |
},
|
| 139 |
{
|
| 140 |
"id": "nchw_inference_vec4",
|
| 141 |
"priority": 100,
|
| 142 |
+
"when": ["dtypes.T == \"f32\"", "ranks.x >= 3", "inner(shapes.x, 1) % 4 == 0"],
|
| 143 |
"passes": [
|
| 144 |
{
|
| 145 |
"id": "main",
|
| 146 |
"name": "BatchNormalization.InferenceVec4",
|
| 147 |
"shader": "batch-normalization-nchw-vec4.wgsl.jinja",
|
| 148 |
+
"bindings": ["x_2", "scale", "bias", "input_mean", "input_var", "y_2", "params_4"],
|
| 149 |
+
"dispatch": {
|
| 150 |
+
"x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 151 |
+
"y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
|
| 152 |
+
"z": 1
|
| 153 |
+
}
|
| 154 |
}
|
| 155 |
]
|
| 156 |
}
|
build/webgpu/metadata.json
CHANGED
|
@@ -1,20 +1,29 @@
|
|
| 1 |
{
|
| 2 |
"name": "ai.onnx.BatchNormalization",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
-
"batch-normalization-nc-vec4.wgsl.jinja": "
|
| 11 |
-
"batch-normalization-nchw-vec4.wgsl.jinja": "
|
| 12 |
-
"batch-normalization-nchw.wgsl.jinja": "
|
| 13 |
-
"
|
| 14 |
-
"
|
| 15 |
-
"
|
|
|
|
| 16 |
}
|
| 17 |
},
|
| 18 |
-
"provenance": { "kernel": { "sha": "
|
| 19 |
-
"webgpu": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "ai.onnx.BatchNormalization",
|
| 3 |
+
"id": "_ai_onnx_batchnormalization_webgpu_4d2bfed",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
+
"batch-normalization-nc-vec4.wgsl.jinja": "yZ3Hf6SM+D6EW4GvvG5Q0N5tuxQlys5l78zAfqwH0IE=",
|
| 11 |
+
"batch-normalization-nchw-flat-vec4.wgsl.jinja": "ZU77NvidV7PpSQC2Fhc/pDgtSlG6EypswF7MWBi+qzw=",
|
| 12 |
+
"batch-normalization-nchw-vec4.wgsl.jinja": "L4z18z+y+0qESVx8FkmHJhTORNeEAOPz1KoV/e7WkCE=",
|
| 13 |
+
"batch-normalization-nchw.wgsl.jinja": "bv0TT2Ry2691M8bUjXYC1tlsrPZ+vPsGgdcUKSj59pg=",
|
| 14 |
+
"bench.json": "yPXcRY22XiVLwm5V3NsowDKaM6BKq2z0Batz3+SR+cI=",
|
| 15 |
+
"manifest.json": "PRmol3fOKZDuk/f21hi3uxRSbxsOhP7RQd+KA2zkW0E=",
|
| 16 |
+
"test.json": "AAN+Vhz8gU2l1tY8tLPNyRAYMpP+8dGalRvLSrzR/5M="
|
| 17 |
}
|
| 18 |
},
|
| 19 |
+
"provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
|
| 20 |
+
"webgpu": {
|
| 21 |
+
"manifestSpec": "2.0",
|
| 22 |
+
"variants": {
|
| 23 |
+
"inference_scalar": ["batch-normalization-nchw.wgsl.jinja"],
|
| 24 |
+
"nc_inference_vec4": ["batch-normalization-nc-vec4.wgsl.jinja"],
|
| 25 |
+
"nchw_inference_flat_vec4": ["batch-normalization-nchw-flat-vec4.wgsl.jinja"],
|
| 26 |
+
"nchw_inference_vec4": ["batch-normalization-nchw-vec4.wgsl.jinja"]
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
}
|
build/webgpu/test.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
{
|
| 2 |
-
"op": "ai.onnx.BatchNormalization",
|
| 3 |
"fixtureArrays": {
|
| 4 |
"ort_positive_single_channel_7x7_exact_default_epsilon_input_x": [0.329876, -0.287158, -0.411425, 0.473621, 0.18156, -0.170596, -0.329516, -0.170733, -0.121664, 0.4372, -0.485668, 0.218049, -0.360263, 0.107016, 0.45358, 0.325056, 0.15995, 0.098852, -0.283453, -0.373051, 0.257542, 0.0614853, -0.0592363, 0.434488, -0.0179583, 0.398374, -0.451602, -0.132009, -0.174468, -0.0247169, 0.418897, -0.47159, -0.131925, 0.470943, 0.118357, 0.155664, 0.370062, -0.279229, 0.240311, -0.451034, 0.249178, -0.294496, 0.13683, -0.0806475, -0.309849, -0.450604, -0.28048, -0.420197, -0.433369]
|
| 5 |
},
|
|
@@ -83,7 +82,7 @@
|
|
| 83 |
"provenance": {
|
| 84 |
"source": "onnxruntime/test/providers/cpu/nn/batch_norm_op_test.cc",
|
| 85 |
"test": "BatchNormTest.PositiveTestCase",
|
| 86 |
-
"notes": "
|
| 87 |
},
|
| 88 |
"attrs": { "epsilon": 0.00001 },
|
| 89 |
"inputs": {
|
|
@@ -190,7 +189,7 @@
|
|
| 190 |
"provenance": {
|
| 191 |
"source": "onnxruntime/test/providers/cpu/nn/batch_norm_op_test.cc",
|
| 192 |
"test": "BatchNormTest.PositiveTestCase",
|
| 193 |
-
"notes": "
|
| 194 |
},
|
| 195 |
"attrs": { "epsilon": 0.25 },
|
| 196 |
"inputs": {
|
|
@@ -450,7 +449,7 @@
|
|
| 450 |
"provenance": {
|
| 451 |
"source": "onnxruntime/test/providers/cpu/nn/batch_norm_op_test.cc",
|
| 452 |
"test": "BatchNormTest.BatchNorm2d_fp16",
|
| 453 |
-
"notes": "
|
| 454 |
},
|
| 455 |
"attrs": { "epsilon": 0.00001 },
|
| 456 |
"inputs": {
|
|
@@ -615,6 +614,69 @@
|
|
| 615 |
"inputVar": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.5, 2.0] } }
|
| 616 |
},
|
| 617 |
"outputs": { "y": { "dtype": "float32", "shape": [1, 2, 2, 1, 2, 1, 2, 2], "tolerance": 0.000001 } }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
}
|
| 619 |
]
|
| 620 |
}
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"fixtureArrays": {
|
| 3 |
"ort_positive_single_channel_7x7_exact_default_epsilon_input_x": [0.329876, -0.287158, -0.411425, 0.473621, 0.18156, -0.170596, -0.329516, -0.170733, -0.121664, 0.4372, -0.485668, 0.218049, -0.360263, 0.107016, 0.45358, 0.325056, 0.15995, 0.098852, -0.283453, -0.373051, 0.257542, 0.0614853, -0.0592363, 0.434488, -0.0179583, 0.398374, -0.451602, -0.132009, -0.174468, -0.0247169, 0.418897, -0.47159, -0.131925, 0.470943, 0.118357, 0.155664, 0.370062, -0.279229, 0.240311, -0.451034, 0.249178, -0.294496, 0.13683, -0.0806475, -0.309849, -0.450604, -0.28048, -0.420197, -0.433369]
|
| 4 |
},
|
|
|
|
| 82 |
"provenance": {
|
| 83 |
"source": "onnxruntime/test/providers/cpu/nn/batch_norm_op_test.cc",
|
| 84 |
"test": "BatchNormTest.PositiveTestCase",
|
| 85 |
+
"notes": "On the vec4 inference path, a subnormal scale must not collapse an otherwise ordinary normalized channel to zero."
|
| 86 |
},
|
| 87 |
"attrs": { "epsilon": 0.00001 },
|
| 88 |
"inputs": {
|
|
|
|
| 189 |
"provenance": {
|
| 190 |
"source": "onnxruntime/test/providers/cpu/nn/batch_norm_op_test.cc",
|
| 191 |
"test": "BatchNormTest.PositiveTestCase",
|
| 192 |
+
"notes": "Zero variance with a large epsilon must still produce a finite, correctly scaled output."
|
| 193 |
},
|
| 194 |
"attrs": { "epsilon": 0.25 },
|
| 195 |
"inputs": {
|
|
|
|
| 449 |
"provenance": {
|
| 450 |
"source": "onnxruntime/test/providers/cpu/nn/batch_norm_op_test.cc",
|
| 451 |
"test": "BatchNormTest.BatchNorm2d_fp16",
|
| 452 |
+
"notes": "A compact rank-4 float16 tensor exercises two-dimensional inference normalization."
|
| 453 |
},
|
| 454 |
"attrs": { "epsilon": 0.00001 },
|
| 455 |
"inputs": {
|
|
|
|
| 614 |
"inputVar": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.5, 2.0] } }
|
| 615 |
},
|
| 616 |
"outputs": { "y": { "dtype": "float32", "shape": [1, 2, 2, 1, 2, 1, 2, 2], "tolerance": 0.000001 } }
|
| 617 |
+
},
|
| 618 |
+
{
|
| 619 |
+
"name": "nc_inference_rank2_vec4_large_mean_small_residual",
|
| 620 |
+
"provenance": {
|
| 621 |
+
"notes": "Inputs sit within 0.1..0.4 of channel means of magnitude 125..1000 with variance 1e-6 (alpha = 1000, epsilon 0). The expanded form x*alpha + (bias - mean*alpha) rounds two ~1e6 products to a 1/16 ulp before cancelling and loses ~0.04 of a ~100 result; (x - mean)*alpha + bias keeps the residual exact."
|
| 622 |
+
},
|
| 623 |
+
"attrs": { "epsilon": 0 },
|
| 624 |
+
"inputs": {
|
| 625 |
+
"x": {
|
| 626 |
+
"dtype": "float32",
|
| 627 |
+
"shape": [2, 8],
|
| 628 |
+
"data": {
|
| 629 |
+
"kind": "values",
|
| 630 |
+
"values": [1000.4, -1000.1999999999999, 500.9, -500.5, 250.4, -249.79999999999998, 126.30000000000001, -125.5, 1000.1999999999999, -1000.4, 500.5, -500.9, 249.79999999999998, -250.4, 125.5, -126.30000000000001]
|
| 631 |
+
}
|
| 632 |
+
},
|
| 633 |
+
"scale": { "dtype": "float32", "shape": [8], "data": { "kind": "constant", "value": 1.0 } },
|
| 634 |
+
"b": {
|
| 635 |
+
"dtype": "float32",
|
| 636 |
+
"shape": [8],
|
| 637 |
+
"data": { "kind": "values", "values": [0.5, -0.5, 0.25, -0.25, 0.125, -0.125, 0.0625, -0.0625] }
|
| 638 |
+
},
|
| 639 |
+
"inputMean": {
|
| 640 |
+
"dtype": "float32",
|
| 641 |
+
"shape": [8],
|
| 642 |
+
"data": { "kind": "values", "values": [1000.3, -1000.3, 500.7, -500.7, 250.1, -250.1, 125.9, -125.9] }
|
| 643 |
+
},
|
| 644 |
+
"inputVar": { "dtype": "float32", "shape": [8], "data": { "kind": "constant", "value": 0.000001 } }
|
| 645 |
+
},
|
| 646 |
+
"outputs": { "y": { "dtype": "float32", "shape": [2, 8], "tolerance": 0.002 } }
|
| 647 |
+
},
|
| 648 |
+
{
|
| 649 |
+
"name": "nchw_inference_plane9_flat_vec4",
|
| 650 |
+
"provenance": {
|
| 651 |
+
"source": "ONNX BatchNormalization-15 specification",
|
| 652 |
+
"notes": "A 3x3 channel plane is not a multiple of four, so four consecutive elements can belong to two channels and each channel's own scale, bias, mean and variance must still reach its own elements. The parameters differ per channel so a group taken from the wrong channel changes the result."
|
| 653 |
+
},
|
| 654 |
+
"attrs": { "epsilon": 0.00001 },
|
| 655 |
+
"inputs": {
|
| 656 |
+
"x": {
|
| 657 |
+
"dtype": "float32",
|
| 658 |
+
"shape": [2, 2, 3, 3],
|
| 659 |
+
"data": {
|
| 660 |
+
"kind": "values",
|
| 661 |
+
"values": [1.0, 1.145098, 1.126169, 0.970775, 0.726453, 0.451691, 0.205283, 0.035887, -0.026454, 0.024608, 0.170881, 0.373162, 0.578522, 0.730102, 0.77774, 0.687602, 0.449162, 0.078279, -0.384177, -0.878017, -1.332846, -1.679122, -1.859379, -1.837756, -1.606254, -1.186673, -0.627873, 0.001237, 0.621873, 1.15709, 1.543172, 1.738877, 1.731074, 1.535902, 1.195315, 0.76962]
|
| 662 |
+
}
|
| 663 |
+
},
|
| 664 |
+
"scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.0, 0.5] } },
|
| 665 |
+
"b": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 1.0] } },
|
| 666 |
+
"inputMean": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.25, -0.5] } },
|
| 667 |
+
"inputVar": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [4.0, 0.25] } }
|
| 668 |
+
},
|
| 669 |
+
"outputs": {
|
| 670 |
+
"y": {
|
| 671 |
+
"dtype": "float32",
|
| 672 |
+
"shape": [2, 2, 3, 3],
|
| 673 |
+
"data": {
|
| 674 |
+
"kind": "values",
|
| 675 |
+
"values": [0.375, 0.447548, 0.438084, 0.360387, 0.238226, 0.100845, -0.022358, -0.107056, -0.138227, 1.524598, 1.670868, 1.873145, 2.0785, 2.230077, 2.277714, 2.187578, 1.949143, 1.578267, -0.317088, -0.564008, -0.791422, -0.96456, -1.054688, -1.043877, -0.928126, -0.718336, -0.438936, 1.501227, 2.121851, 2.657057, 3.043131, 3.238832, 3.231029, 3.035861, 2.695281, 2.269595]
|
| 676 |
+
},
|
| 677 |
+
"tolerance": 0.00001
|
| 678 |
+
}
|
| 679 |
+
}
|
| 680 |
}
|
| 681 |
]
|
| 682 |
}
|