Xenova HF Staff commited on
Commit
8bfadd9
·
verified ·
1 Parent(s): 495ad1c

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,75 @@
1
  ---
 
2
  license: apache-2.0
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ library_name: kernels
3
  license: apache-2.0
4
+ tags:
5
+ - kernel
6
+ - webgpu
7
+ - wgsl
8
  ---
9
+ # ai.onnx.LogSoftmax
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 13
12
+
13
+ ## Description
14
+
15
+ Computes `log(softmax(input, axis))` along a single axis using a numerically stable shifted reduction. The output has the same shape as the input.
16
+
17
+ See the [ONNX `LogSoftmax` spec](https://onnx.ai/onnx/operators/onnx__LogSoftmax.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `input` | `x` | `T` | — | — | The input tensor of rank >= 1. | required |
24
+
25
+ ## Outputs
26
+
27
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
28
+ | --- | --- | --- | --- | --- | --- | --- |
29
+ | `output` | `y` | `T` | same as `input` | same as `input` | The log-softmax values; same shape as the input. | required |
30
+
31
+ ## Attributes
32
+
33
+ Default values (overridable per request):
34
+
35
+ | Attribute | Default | Description |
36
+ | --- | --- | --- |
37
+ | `axis` | `-1` | The axis along which log-softmax is computed. Negative values count from the end; the default `-1` operates over the last dimension. Accepted range is `[-r, r-1]` where `r` is the input rank. |
38
+
39
+ ## Type constraints
40
+
41
+ | Variable | Allowed dtypes |
42
+ | --- | --- |
43
+ | `T` | `float32`, `float16` |
44
+
45
+ ## Files
46
+
47
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
48
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
49
+ - [`test.json`](build/webgpu/test.json) — correctness cases
50
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
51
+ - [`softmax-longrow-normalize.wgsl.jinja`](build/webgpu/softmax-longrow-normalize.wgsl.jinja)
52
+ - [`softmax-longrow-stats.wgsl.jinja`](build/webgpu/softmax-longrow-stats.wgsl.jinja)
53
+ - [`softmax-normalize.wgsl.jinja`](build/webgpu/softmax-normalize.wgsl.jinja)
54
+ - [`softmax-online-packed-rows.wgsl.jinja`](build/webgpu/softmax-online-packed-rows.wgsl.jinja)
55
+ - [`softmax-online.wgsl.jinja`](build/webgpu/softmax-online.wgsl.jinja)
56
+ - [`softmax-row-stage-strided-vec4.wgsl.jinja`](build/webgpu/softmax-row-stage-strided-vec4.wgsl.jinja)
57
+ - [`softmax-row-stage.wgsl.jinja`](build/webgpu/softmax-row-stage.wgsl.jinja)
58
+ - [`softmax-strided-online-lane.wgsl.jinja`](build/webgpu/softmax-strided-online-lane.wgsl.jinja)
59
+ - [`softmax-strided-packed4-tail.wgsl.jinja`](build/webgpu/softmax-strided-packed4-tail.wgsl.jinja)
60
+
61
+ ## Use with `@huggingface/kernels`
62
+
63
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
64
+ It then allocates the result tensors automatically.
65
+
66
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
67
+
68
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
69
+
70
+ ```js
71
+ import { getKernel } from "@huggingface/kernels";
72
+
73
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.LogSoftmax", { version: 1 });
74
+ const { y } = await kernel({ x: { data: xData, shape: [1, 3] } });
75
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.LogSoftmax",
3
+ "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
4
+ "cases": [
5
+ {
6
+ "name": "attention_scores",
7
+ "inputs": { "x": { "dtype": "float32", "shape": [32, 128] } },
8
+ "outputs": { "y": { "dtype": "float32", "shape": [32, 128] } }
9
+ },
10
+ {
11
+ "name": "logsoftmax-f32-1024x2048",
12
+ "preset": "smoke",
13
+ "vars": { "dtype": "float32", "rows": 1024, "cols": 2048 },
14
+ "attrs": { "axis": 1 },
15
+ "inputs": { "x": { "shape": [1024, 2048], "dtype": "float32", "dist": "normal", "seed": 211, "scale": 2 } },
16
+ "outputs": { "y": { "shape": [1024, 2048], "dtype": "float32" } },
17
+ "bench": {
18
+ "primary": true,
19
+ "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }]
20
+ }
21
+ },
22
+ {
23
+ "name": "logsoftmax-f32-16384x512",
24
+ "preset": "stress",
25
+ "vars": { "dtype": "float32", "rows": 16384, "cols": 512 },
26
+ "attrs": { "axis": 1 },
27
+ "inputs": { "x": { "shape": [16384, 512], "dtype": "float32", "dist": "normal", "seed": 212, "scale": 2 } },
28
+ "outputs": { "y": { "shape": [16384, 512], "dtype": "float32" } },
29
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }] }
30
+ },
31
+ {
32
+ "name": "logsoftmax-logits-1x262144",
33
+ "preset": "model",
34
+ "vars": { "dtype": "float32", "rows": 1, "cols": 262144 },
35
+ "attrs": { "axis": 1 },
36
+ "inputs": { "x": { "shape": [1, 262144], "dtype": "float32", "dist": "normal", "seed": 213, "scale": 4 } },
37
+ "outputs": { "y": { "shape": [1, 262144], "dtype": "float32" } },
38
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }] }
39
+ },
40
+ {
41
+ "name": "logsoftmax-f32-strided-axis1-64x1024x64",
42
+ "preset": "stress",
43
+ "vars": { "dtype": "float32", "rows": 64, "mid": 1024, "inner": 64 },
44
+ "attrs": { "axis": 1 },
45
+ "inputs": { "x": { "shape": [64, 1024, 64], "dtype": "float32", "dist": "normal", "seed": 311, "scale": 2 } },
46
+ "outputs": { "y": { "shape": [64, 1024, 64], "dtype": "float32" } },
47
+ "bench": {
48
+ "metrics": [{ "type": "bandwidth", "value": "args.rows * args.mid * args.inner * dtypeBytes(args.dtype) * 2" }]
49
+ }
50
+ },
51
+ {
52
+ "name": "logsoftmax-f16-1024x2048-axis1",
53
+ "preset": "stress",
54
+ "vars": { "dtype": "float16", "rows": 1024, "cols": 2048 },
55
+ "attrs": { "axis": 1 },
56
+ "inputs": { "x": { "shape": [1024, 2048], "dtype": "float16", "dist": "normal", "seed": 312, "scale": 2 } },
57
+ "outputs": { "y": { "shape": [1024, 2048], "dtype": "float16" } },
58
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }] }
59
+ },
60
+ {
61
+ "name": "logsoftmax-f32-align-cliff-1024x2046",
62
+ "preset": "stress",
63
+ "vars": { "dtype": "float32", "rows": 1024, "cols": 2046 },
64
+ "attrs": { "axis": 1 },
65
+ "inputs": { "x": { "shape": [1024, 2046], "dtype": "float32", "dist": "normal", "seed": 313, "scale": 2 } },
66
+ "outputs": { "y": { "shape": [1024, 2046], "dtype": "float32" } },
67
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }] }
68
+ },
69
+ {
70
+ "name": "logsoftmax-f32-tallwide-70000x2048",
71
+ "preset": "model",
72
+ "vars": { "dtype": "float32", "rows": 70000, "cols": 2048 },
73
+ "attrs": { "axis": 1 },
74
+ "inputs": { "x": { "shape": [70000, 2048], "dtype": "float32", "dist": "normal", "seed": 314, "scale": 2 } },
75
+ "outputs": { "y": { "shape": [70000, 2048], "dtype": "float32" } },
76
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }] }
77
+ },
78
+ {
79
+ "name": "logsoftmax-f32-nosg-3pass-occupancy-131072x32",
80
+ "preset": "stress",
81
+ "vars": { "dtype": "float32", "rows": 131072, "cols": 32 },
82
+ "attrs": { "axis": 1 },
83
+ "inputs": { "x": { "shape": [131072, 32], "dtype": "float32", "dist": "normal", "seed": 411, "scale": 2 } },
84
+ "outputs": { "y": { "shape": [131072, 32], "dtype": "float32" } },
85
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }] }
86
+ },
87
+ {
88
+ "name": "logsoftmax-f32-strided-axis1-64x2048x130-packed4-control",
89
+ "preset": "stress",
90
+ "provenance": {
91
+ "source": "strided-axis selector crossover",
92
+ "notes": "Below-crossover control for the large-inner tail pathology. At 8,320 independent strided rows the cooperative packed-4 reducer still beats the serial-lane capacity path."
93
+ },
94
+ "vars": { "dtype": "float32", "outer": 64, "axisDim": 2048, "inner": 130 },
95
+ "attrs": { "axis": 1 },
96
+ "inputs": { "x": { "shape": [64, 2048, 130], "dtype": "float32", "dist": "normal", "seed": 414, "scale": 2 } },
97
+ "outputs": { "y": { "shape": [64, 2048, 130], "dtype": "float32", "dist": "empty" } },
98
+ "bench": {
99
+ "metrics": [
100
+ { "type": "bandwidth", "value": "args.outer * args.axisDim * args.inner * dtypeBytes(args.dtype) * 2" }
101
+ ]
102
+ }
103
+ },
104
+ {
105
+ "name": "logsoftmax-f32-strided-axis1-96x2048x130-crossover",
106
+ "preset": "stress",
107
+ "provenance": {
108
+ "source": "strided-axis selector crossover",
109
+ "notes": "Above-crossover probe for the large-inner tail pathology. At 12,480 independent strided rows the serial-lane capacity path overtakes the cooperative packed-4 reducer."
110
+ },
111
+ "vars": { "dtype": "float32", "outer": 96, "axisDim": 2048, "inner": 130 },
112
+ "attrs": { "axis": 1 },
113
+ "inputs": { "x": { "shape": [96, 2048, 130], "dtype": "float32", "dist": "normal", "seed": 413, "scale": 2 } },
114
+ "outputs": { "y": { "shape": [96, 2048, 130], "dtype": "float32", "dist": "empty" } },
115
+ "bench": {
116
+ "metrics": [
117
+ { "type": "bandwidth", "value": "args.outer * args.axisDim * args.inner * dtypeBytes(args.dtype) * 2" }
118
+ ]
119
+ }
120
+ },
121
+ {
122
+ "name": "logsoftmax-f32-strided-axis1-120x2048x130-large-inner",
123
+ "preset": "stress",
124
+ "provenance": {
125
+ "source": "authored for variant coverage",
126
+ "notes": "Inner=130 exercises the two-element tail of the scalar-aligned packed-4 online reducer while sharing one workgroup across four adjacent rows."
127
+ },
128
+ "vars": { "dtype": "float32", "outer": 120, "axisDim": 2048, "inner": 130 },
129
+ "attrs": { "axis": 1 },
130
+ "inputs": { "x": { "shape": [120, 2048, 130], "dtype": "float32", "dist": "normal", "seed": 412, "scale": 2 } },
131
+ "outputs": { "y": { "shape": [120, 2048, 130], "dtype": "float32", "dist": "empty" } },
132
+ "bench": {
133
+ "metrics": [
134
+ { "type": "bandwidth", "value": "args.outer * args.axisDim * args.inner * dtypeBytes(args.dtype) * 2" }
135
+ ]
136
+ }
137
+ },
138
+ {
139
+ "name": "logsoftmax-f32-8192x512-midwidth",
140
+ "provenance": {
141
+ "notes": "Covers the 512-column midpoint between the 128- and 2048-column cases and probes the width term in online_wg_vec4's selector."
142
+ },
143
+ "preset": "smoke",
144
+ "vars": { "dtype": "float32", "rows": 8192, "cols": 512 },
145
+ "attrs": { "axis": 1 },
146
+ "inputs": { "x": { "shape": [8192, 512], "dtype": "float32", "dist": "normal", "seed": 215, "scale": 2 } },
147
+ "outputs": { "y": { "shape": [8192, 512], "dtype": "float32" } },
148
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.rows * args.cols * dtypeBytes(args.dtype) * 2" }] }
149
+ }
150
+ ]
151
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,571 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "LogSoftmax",
4
+ "sinceVersion": 13,
5
+ "description": "Computes `log(softmax(input, axis))` along a single axis using a numerically stable shifted reduction. The output has the same shape as the input.",
6
+ "inputs": [{ "role": "input", "dtype": "T", "description": "The input tensor of rank >= 1." }],
7
+ "outputs": [
8
+ {
9
+ "role": "output",
10
+ "dtype": "T",
11
+ "rank": "ranks.input",
12
+ "description": "The log-softmax values; same shape as the input.",
13
+ "shape": "shapes.input"
14
+ }
15
+ ],
16
+ "attributes": { "axis": -1 },
17
+ "attributeDescriptions": {
18
+ "axis": "The axis along which log-softmax is computed. Negative values count from the end; the default `-1` operates over the last dimension. Accepted range is `[-r, r-1]` where `r` is the input rank."
19
+ },
20
+ "typeConstraints": { "T": ["float32", "float16"] },
21
+ "args": {
22
+ "x": { "kind": "tensor", "semantic": "input", "role": "input" },
23
+ "y": { "kind": "tensor", "semantic": "output", "role": "output" }
24
+ },
25
+ "tunables": {
26
+ "WORKGROUP_SIZE": 256,
27
+ "BLOCK_COLS": 1024,
28
+ "LONGROW_SPLIT_MIN_COLS": 65536,
29
+ "STRIDED_CAPACITY_MIN_ROWS": 12288
30
+ },
31
+ "derive": {
32
+ "storageBufferLimit": "min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
33
+ "axis": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x",
34
+ "lastCols": "dim(shapes.x, ranks.x - 1)",
35
+ "lastRows": "rows(shapes.x, ranks.x - 1)",
36
+ "axisInner": "inner(shapes.x, axis)",
37
+ "axisOuter": "outer(shapes.x, axis)",
38
+ "axisRows": "axisOuter * axisInner",
39
+ "longrowBlocks": "ceilDiv(lastCols, tunables.BLOCK_COLS)",
40
+ "lastRowScratchFits": "4 * lastRows <= storageBufferLimit",
41
+ "axisRowScratchFits": "4 * axisRows <= storageBufferLimit",
42
+ "longrowBlockScratchFits": "4 * lastRows * longrowBlocks <= storageBufferLimit",
43
+ "lastDispatchFits": "ceilDiv(lastRows, device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension",
44
+ "axisDispatchFits": "ceilDiv(axisRows, device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension",
45
+ "axisThreadDispatchFits": "ceilDiv(ceilDiv(axisRows, tunables.WORKGROUP_SIZE), device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension",
46
+ "lastAxisOk": "numel(shapes.x) == numel(shapes.y) and ranks.x >= 1 and (attrs.axis == -1 or attrs.axis == ranks.x - 1) and f16Ok(dtypes.T)",
47
+ "stridedAxisOk": "numel(shapes.x) == numel(shapes.y) and ranks.x >= 2 and attrs.axis + ranks.x >= 0 and attrs.axis < ranks.x and not (attrs.axis == -1 or attrs.axis == ranks.x - 1)"
48
+ },
49
+ "bindingSets": {
50
+ "longrowBlockStats": [
51
+ { "name": "x", "arg": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
52
+ { "name": "blockMax", "semantic": "blockMax", "buffer": { "type": "storage" }, "elementType": "f32" },
53
+ { "name": "blockSum", "semantic": "blockSum", "buffer": { "type": "storage" }, "elementType": "f32" },
54
+ {
55
+ "name": "params",
56
+ "semantic": "kernel.params",
57
+ "buffer": { "type": "uniform" },
58
+ "struct": {
59
+ "name": "Params",
60
+ "fields": [
61
+ { "name": "cols", "type": "u32", "value": "lastCols" },
62
+ { "name": "blocks", "type": "u32", "value": "ceilDiv(lastCols, tunables.BLOCK_COLS)" }
63
+ ]
64
+ }
65
+ }
66
+ ],
67
+ "longrowRowStats": [
68
+ { "name": "blockMax", "semantic": "blockMax", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
69
+ { "name": "blockSum", "semantic": "blockSum", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
70
+ { "name": "rowMax", "semantic": "rowMax", "buffer": { "type": "storage" }, "elementType": "f32" },
71
+ { "name": "rowSum", "semantic": "rowSum", "buffer": { "type": "storage" }, "elementType": "f32" },
72
+ {
73
+ "name": "params",
74
+ "semantic": "kernel.params",
75
+ "buffer": { "type": "uniform" },
76
+ "struct": {
77
+ "name": "Params",
78
+ "fields": [{ "name": "blocks", "type": "u32", "value": "ceilDiv(lastCols, tunables.BLOCK_COLS)" }]
79
+ }
80
+ }
81
+ ],
82
+ "longrowNormalize": [
83
+ { "name": "x", "arg": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
84
+ { "name": "rowMax", "semantic": "rowMax", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
85
+ { "name": "rowSum", "semantic": "rowSum", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
86
+ { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$T" },
87
+ {
88
+ "name": "params",
89
+ "semantic": "kernel.params",
90
+ "buffer": { "type": "uniform" },
91
+ "struct": { "name": "Params", "fields": [{ "name": "cols", "type": "u32", "value": "lastCols" }] }
92
+ }
93
+ ],
94
+ "online_vec4": [
95
+ {
96
+ "name": "x",
97
+ "arg": "x",
98
+ "semantic": "X",
99
+ "buffer": { "type": "read-only-storage" },
100
+ "elementType": "$vectorScalar"
101
+ },
102
+ { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" },
103
+ {
104
+ "name": "params",
105
+ "semantic": "kernel.params",
106
+ "buffer": { "type": "uniform" },
107
+ "struct": {
108
+ "name": "Params",
109
+ "fields": [
110
+ { "name": "rows", "type": "u32", "value": "lastRows" },
111
+ { "name": "vecCols", "type": "u32", "value": "lastCols / 4" }
112
+ ]
113
+ }
114
+ }
115
+ ],
116
+ "online_scalar": [
117
+ {
118
+ "name": "x",
119
+ "arg": "x",
120
+ "semantic": "input",
121
+ "buffer": { "type": "read-only-storage" },
122
+ "elementType": "$scalar"
123
+ },
124
+ { "name": "y", "arg": "y", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "$scalar" },
125
+ {
126
+ "name": "params",
127
+ "semantic": "kernel.params",
128
+ "buffer": { "type": "uniform" },
129
+ "struct": {
130
+ "name": "Params",
131
+ "fields": [
132
+ { "name": "rows", "type": "u32", "value": "lastRows" },
133
+ { "name": "cols", "type": "u32", "value": "lastCols" }
134
+ ]
135
+ }
136
+ }
137
+ ],
138
+ "stableReduceMax": [
139
+ { "name": "x", "arg": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
140
+ { "name": "rowMax", "semantic": "rowMax", "buffer": { "type": "storage" }, "elementType": "f32" },
141
+ {
142
+ "name": "params",
143
+ "semantic": "kernel.params",
144
+ "buffer": { "type": "uniform" },
145
+ "struct": {
146
+ "name": "Params",
147
+ "fields": [
148
+ { "name": "rows", "type": "u32", "value": "lastRows" },
149
+ { "name": "cols", "type": "u32", "value": "lastCols" }
150
+ ]
151
+ }
152
+ }
153
+ ],
154
+ "stableExpSum": [
155
+ { "name": "x", "arg": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
156
+ { "name": "rowMax", "semantic": "rowMax", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
157
+ { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$T" },
158
+ { "name": "rowSum", "semantic": "rowSum", "buffer": { "type": "storage" }, "elementType": "f32" },
159
+ {
160
+ "name": "params",
161
+ "semantic": "kernel.params",
162
+ "buffer": { "type": "uniform" },
163
+ "struct": {
164
+ "name": "Params",
165
+ "fields": [
166
+ { "name": "rows", "type": "u32", "value": "lastRows" },
167
+ { "name": "cols", "type": "u32", "value": "lastCols" }
168
+ ]
169
+ }
170
+ }
171
+ ],
172
+ "normalize": [
173
+ { "name": "x", "arg": "x", "semantic": "input", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
174
+ { "name": "rowMax", "semantic": "rowMax", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
175
+ { "name": "rowSum", "semantic": "rowSum", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
176
+ { "name": "y", "arg": "y", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "$T" },
177
+ {
178
+ "name": "params",
179
+ "semantic": "kernel.params",
180
+ "buffer": { "type": "uniform" },
181
+ "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.x)" }] }
182
+ }
183
+ ],
184
+ "stableNormalize": [
185
+ { "name": "x", "arg": "x", "semantic": "input", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
186
+ { "name": "rowMax", "semantic": "rowMax", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
187
+ { "name": "rowSum", "semantic": "rowSum", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
188
+ { "name": "y", "arg": "y", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "$T" },
189
+ {
190
+ "name": "params",
191
+ "semantic": "kernel.params",
192
+ "buffer": { "type": "uniform" },
193
+ "struct": {
194
+ "name": "Params",
195
+ "fields": [
196
+ { "name": "count", "type": "u32", "value": "numel(shapes.x)" },
197
+ { "name": "cols", "type": "u32", "value": "lastCols" }
198
+ ]
199
+ }
200
+ }
201
+ ],
202
+ "stridedScalarReduce": [
203
+ { "name": "x", "arg": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
204
+ { "name": "rowMax", "semantic": "rowMax", "buffer": { "type": "storage" }, "elementType": "f32" }
205
+ ],
206
+ "stridedScalarExpSum": [
207
+ { "name": "x", "arg": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
208
+ { "name": "rowMax", "semantic": "rowMax", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
209
+ { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$T" },
210
+ { "name": "rowSum", "semantic": "rowSum", "buffer": { "type": "storage" }, "elementType": "f32" }
211
+ ],
212
+ "stridedOnline": [
213
+ { "name": "x", "arg": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
214
+ { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$T" }
215
+ ]
216
+ },
217
+ "variants": [
218
+ {
219
+ "id": "longrow_split",
220
+ "priority": 30,
221
+ "when": ["lastCols >= tunables.LONGROW_SPLIT_MIN_COLS", "rows(shapes.x, ranks.x - 1) <= device.limits.maxComputeWorkgroupsPerDimension", "ceilDiv(dim(shapes.x, ranks.x - 1), tunables.BLOCK_COLS) <= device.limits.maxComputeWorkgroupsPerDimension", "lastRowScratchFits", "longrowBlockScratchFits", "lastAxisOk"],
222
+ "intermediates": [
223
+ {
224
+ "id": "blockMax",
225
+ "dtype": "float32",
226
+ "shape": "[rows(shapes.x, ranks.x - 1) * ceilDiv(dim(shapes.x, ranks.x - 1), tunables.BLOCK_COLS)]"
227
+ },
228
+ {
229
+ "id": "blockSum",
230
+ "dtype": "float32",
231
+ "shape": "[rows(shapes.x, ranks.x - 1) * ceilDiv(dim(shapes.x, ranks.x - 1), tunables.BLOCK_COLS)]"
232
+ },
233
+ { "id": "rowMax", "dtype": "float32", "shape": "[rows(shapes.x, ranks.x - 1)]" },
234
+ { "id": "rowSum", "dtype": "float32", "shape": "[rows(shapes.x, ranks.x - 1)]" }
235
+ ],
236
+ "passes": [
237
+ {
238
+ "id": "block_stats",
239
+ "name": "LogSoftmax.LongRowBlockStats",
240
+ "source": { "shader": "softmax-longrow-stats.wgsl.jinja", "inputs": { "stage": "\"block\"" } },
241
+ "bindings": "longrowBlockStats",
242
+ "dispatch": {
243
+ "x": "ceilDiv(dim(shapes.x, ranks.x - 1), tunables.BLOCK_COLS)",
244
+ "y": "rows(shapes.x, ranks.x - 1)"
245
+ }
246
+ },
247
+ {
248
+ "id": "row_stats",
249
+ "name": "LogSoftmax.LongRowStats",
250
+ "source": { "shader": "softmax-longrow-stats.wgsl.jinja", "inputs": { "stage": "\"row\"" } },
251
+ "bindings": "longrowRowStats",
252
+ "dispatch": { "x": "rows(shapes.x, ranks.x - 1)" }
253
+ },
254
+ {
255
+ "id": "normalize",
256
+ "name": "LogSoftmax.LongRowNormalize",
257
+ "source": { "shader": "softmax-longrow-normalize.wgsl.jinja", "inputs": { "op": "\"logsoftmax\"" } },
258
+ "bindings": "longrowNormalize",
259
+ "dispatch": {
260
+ "x": "ceilDiv(dim(shapes.x, ranks.x - 1), tunables.BLOCK_COLS)",
261
+ "y": "rows(shapes.x, ranks.x - 1)"
262
+ }
263
+ }
264
+ ]
265
+ },
266
+ {
267
+ "id": "online_wg_vec4",
268
+ "priority": 16,
269
+ "when": ["(device.features.has(\"subgroups\") and dim(shapes.x, ranks.x - 1) >= 1024) or (not device.features.has(\"subgroups\") and dim(shapes.x, ranks.x - 1) > 32)", "dim(shapes.x, ranks.x - 1) % 4 == 0", "ceilDiv(rows(shapes.x, ranks.x - 1), device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension", "lastAxisOk"],
270
+ "constants": {
271
+ "combineSubgroups": "device.features.has(\"subgroups\") and dim(shapes.x, ranks.x - 1) >= 1024",
272
+ "scalar": "dtypes.T",
273
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
274
+ "usesF16": "dtypes.T == \"f16\""
275
+ },
276
+ "passes": [
277
+ {
278
+ "id": "main",
279
+ "name": "LogSoftmax.OnlineWorkgroupVec4",
280
+ "source": { "shader": "softmax-online.wgsl.jinja", "inputs": { "op": "\"logsoftmax\"", "useVec4": "true" } },
281
+ "bindings": "online_vec4",
282
+ "dispatch": { "workgroups": "rows(shapes.x, ranks.x - 1)" }
283
+ }
284
+ ]
285
+ },
286
+ {
287
+ "id": "online_wg",
288
+ "priority": 15,
289
+ "when": ["(device.features.has(\"subgroups\") and dim(shapes.x, ranks.x - 1) > 0) or dim(shapes.x, ranks.x - 1) > 32", "ceilDiv(rows(shapes.x, ranks.x - 1), device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension", "lastAxisOk"],
290
+ "constants": {
291
+ "combineSubgroups": "device.features.has(\"subgroups\")",
292
+ "scalar": "dtypes.T",
293
+ "usesF16": "dtypes.T == \"f16\""
294
+ },
295
+ "passes": [
296
+ {
297
+ "id": "main",
298
+ "name": "LogSoftmax.OnlineWorkgroup",
299
+ "source": { "shader": "softmax-online.wgsl.jinja", "inputs": { "op": "\"logsoftmax\"", "useVec4": "false" } },
300
+ "bindings": "online_scalar",
301
+ "dispatch": { "workgroups": "rows(shapes.x, ranks.x - 1)" }
302
+ }
303
+ ]
304
+ },
305
+ {
306
+ "id": "online_packed_rows_vec4_nosg",
307
+ "priority": 17,
308
+ "when": ["dim(shapes.x, ranks.x - 1) > 0", "dim(shapes.x, ranks.x - 1) <= 32", "dim(shapes.x, ranks.x - 1) % 4 == 0", "rows(shapes.x, ranks.x - 1) > 32", "lastAxisOk"],
309
+ "constants": {
310
+ "scalar": "dtypes.T",
311
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
312
+ "usesF16": "dtypes.T == \"f16\"",
313
+ "lanesPerRow": 8,
314
+ "rowsPerWorkgroup": 32
315
+ },
316
+ "passes": [
317
+ {
318
+ "id": "main",
319
+ "name": "LogSoftmax.OnlinePackedRowsVec4NoSg",
320
+ "source": {
321
+ "shader": "softmax-online-packed-rows.wgsl.jinja",
322
+ "inputs": {
323
+ "op": "\"logsoftmax\"",
324
+ "lanesPerRow": "constants.lanesPerRow",
325
+ "rowsPerWorkgroup": "constants.rowsPerWorkgroup"
326
+ }
327
+ },
328
+ "bindings": "online_vec4",
329
+ "dispatch": { "gridStride": "ceilDiv(rows(shapes.x, ranks.x - 1), constants.rowsPerWorkgroup)" }
330
+ }
331
+ ]
332
+ },
333
+ {
334
+ "id": "online_local_vec4_nosg",
335
+ "priority": 16,
336
+ "tunables": { "WORKGROUP_SIZE": 8 },
337
+ "when": ["dim(shapes.x, ranks.x - 1) > 0", "dim(shapes.x, ranks.x - 1) <= 32", "dim(shapes.x, ranks.x - 1) % 4 == 0", "ceilDiv(rows(shapes.x, ranks.x - 1), device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension", "lastAxisOk"],
338
+ "constants": {
339
+ "combineSubgroups": false,
340
+ "scalar": "dtypes.T",
341
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
342
+ "usesF16": "dtypes.T == \"f16\""
343
+ },
344
+ "passes": [
345
+ {
346
+ "id": "main",
347
+ "name": "LogSoftmax.OnlineLocalVec4NoSg",
348
+ "source": { "shader": "softmax-online.wgsl.jinja", "inputs": { "op": "\"logsoftmax\"", "useVec4": "true" } },
349
+ "bindings": "online_vec4",
350
+ "dispatch": { "workgroups": "rows(shapes.x, ranks.x - 1)" }
351
+ }
352
+ ]
353
+ },
354
+ {
355
+ "id": "online_local_nosg",
356
+ "priority": 14,
357
+ "tunables": { "WORKGROUP_SIZE": 32 },
358
+ "when": ["dim(shapes.x, ranks.x - 1) > 0", "dim(shapes.x, ranks.x - 1) <= 32", "ceilDiv(rows(shapes.x, ranks.x - 1), device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension", "lastAxisOk"],
359
+ "constants": { "combineSubgroups": false, "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
360
+ "passes": [
361
+ {
362
+ "id": "main",
363
+ "name": "LogSoftmax.OnlineLocalNoSg",
364
+ "source": { "shader": "softmax-online.wgsl.jinja", "inputs": { "op": "\"logsoftmax\"", "useVec4": "false" } },
365
+ "bindings": "online_scalar",
366
+ "dispatch": { "workgroups": "rows(shapes.x, ranks.x - 1)" }
367
+ }
368
+ ]
369
+ },
370
+ {
371
+ "id": "stable_3pass",
372
+ "priority": 8,
373
+ "when": ["lastAxisOk", "lastDispatchFits", "lastRowScratchFits"],
374
+ "intermediates": [
375
+ { "id": "rowMax", "dtype": "float32", "shape": "[numel(shapes.x) / dim(shapes.x, ranks.x - 1)]" },
376
+ { "id": "rowSum", "dtype": "float32", "shape": "[numel(shapes.x) / dim(shapes.x, ranks.x - 1)]" }
377
+ ],
378
+ "passes": [
379
+ {
380
+ "id": "reduce_max",
381
+ "name": "LogSoftmax.ReduceMax",
382
+ "source": { "shader": "softmax-row-stage.wgsl.jinja", "inputs": { "stage": "\"max\"", "strided": "false" } },
383
+ "bindings": "stableReduceMax",
384
+ "dispatch": { "workgroups": "numel(shapes.x) / dim(shapes.x, ranks.x - 1)" }
385
+ },
386
+ {
387
+ "id": "exp_sum",
388
+ "name": "LogSoftmax.ExpSum",
389
+ "source": {
390
+ "shader": "softmax-row-stage.wgsl.jinja",
391
+ "inputs": { "stage": "\"exp_sum\"", "strided": "false" }
392
+ },
393
+ "bindings": "stableExpSum",
394
+ "dispatch": { "workgroups": "numel(shapes.x) / dim(shapes.x, ranks.x - 1)" }
395
+ },
396
+ {
397
+ "id": "normalize",
398
+ "name": "LogSoftmax.Normalize",
399
+ "source": {
400
+ "shader": "softmax-normalize.wgsl.jinja",
401
+ "inputs": { "strided": "false", "op": "\"logsoftmax\"" }
402
+ },
403
+ "bindings": "stableNormalize",
404
+ "dispatch": { "threads": "numel(shapes.x)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
405
+ }
406
+ ]
407
+ },
408
+ {
409
+ "id": "strided_3pass_vec4",
410
+ "priority": 14,
411
+ "derive": { "axis": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x" },
412
+ "when": ["dtypes.T == \"f32\"", "stridedAxisOk", "inner(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x) % 4 == 0", "axisDispatchFits", "axisRowScratchFits"],
413
+ "intermediates": [
414
+ { "id": "rowMax", "dtype": "float32", "shape": "[numel(shapes.x) / dim(shapes.x, axis)]" },
415
+ { "id": "rowSum", "dtype": "float32", "shape": "[numel(shapes.x) / dim(shapes.x, axis)]" }
416
+ ],
417
+ "passes": [
418
+ {
419
+ "id": "reduce_max",
420
+ "name": "LogSoftmax.StridedReduceMaxVec4",
421
+ "source": {
422
+ "shader": "softmax-row-stage-strided-vec4.wgsl.jinja",
423
+ "inputs": { "stage": "\"max\"", "xShape": "shapes.x", "axis": "axis" }
424
+ },
425
+ "bindings": [
426
+ {
427
+ "name": "x",
428
+ "arg": "x",
429
+ "semantic": "input",
430
+ "buffer": { "type": "read-only-storage" },
431
+ "elementType": "vec4<f32>"
432
+ },
433
+ { "name": "rowMax", "semantic": "rowMax", "buffer": { "type": "storage" }, "elementType": "vec4<f32>" }
434
+ ],
435
+ "dispatch": { "workgroups": "numel(shapes.x) / dim(shapes.x, axis) / 4" }
436
+ },
437
+ {
438
+ "id": "exp_sum",
439
+ "name": "LogSoftmax.StridedExpSumVec4",
440
+ "source": {
441
+ "shader": "softmax-row-stage-strided-vec4.wgsl.jinja",
442
+ "inputs": { "stage": "\"exp_sum\"", "xShape": "shapes.x", "axis": "axis" }
443
+ },
444
+ "bindings": [
445
+ {
446
+ "name": "x",
447
+ "arg": "x",
448
+ "semantic": "input",
449
+ "buffer": { "type": "read-only-storage" },
450
+ "elementType": "vec4<f32>"
451
+ },
452
+ {
453
+ "name": "rowMax",
454
+ "semantic": "rowMax",
455
+ "buffer": { "type": "read-only-storage" },
456
+ "elementType": "vec4<f32>"
457
+ },
458
+ {
459
+ "name": "y",
460
+ "arg": "y",
461
+ "semantic": "output",
462
+ "buffer": { "type": "storage" },
463
+ "elementType": "vec4<f32>"
464
+ },
465
+ { "name": "rowSum", "semantic": "rowSum", "buffer": { "type": "storage" }, "elementType": "vec4<f32>" }
466
+ ],
467
+ "dispatch": { "workgroups": "numel(shapes.x) / dim(shapes.x, axis) / 4" }
468
+ },
469
+ {
470
+ "id": "normalize",
471
+ "name": "LogSoftmax.StridedNormalize",
472
+ "source": {
473
+ "shader": "softmax-normalize.wgsl.jinja",
474
+ "inputs": { "strided": "true", "xShape": "shapes.x", "axis": "axis", "op": "\"logsoftmax\"" }
475
+ },
476
+ "bindings": "normalize",
477
+ "dispatch": { "threads": "numel(shapes.x)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
478
+ }
479
+ ]
480
+ },
481
+ {
482
+ "id": "strided_online_packed4_tail",
483
+ "priority": 18,
484
+ "derive": { "axis": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x" },
485
+ "when": ["dtypes.T == \"f32\"", "stridedAxisOk", "inner(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x) >= 4", "inner(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x) % 4 != 0", "dim(shapes.x, axis) < 2048 or axisRows < tunables.STRIDED_CAPACITY_MIN_ROWS", "axisDispatchFits"],
486
+ "passes": [
487
+ {
488
+ "id": "main",
489
+ "name": "LogSoftmax.StridedOnlinePacked4Tail",
490
+ "source": {
491
+ "shader": "softmax-strided-packed4-tail.wgsl.jinja",
492
+ "inputs": { "xShape": "shapes.x", "axis": "axis" }
493
+ },
494
+ "bindings": [
495
+ {
496
+ "name": "x",
497
+ "arg": "x",
498
+ "semantic": "input",
499
+ "buffer": { "type": "read-only-storage" },
500
+ "elementType": "f32"
501
+ },
502
+ { "name": "y", "arg": "y", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" }
503
+ ],
504
+ "dispatch": {
505
+ "workgroups": "numel(shapes.x) / dim(shapes.x, axis) / inner(shapes.x, axis) * ceilDiv(inner(shapes.x, axis), 4)"
506
+ }
507
+ }
508
+ ]
509
+ },
510
+ {
511
+ "id": "strided_3pass",
512
+ "priority": 12,
513
+ "derive": { "axis": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x" },
514
+ "demoteWhen": ["dtypes.T == \"f32\" and inner(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x) >= 4 and inner(shapes.x, attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.x) % 4 != 0"],
515
+ "when": ["stridedAxisOk", "f16Ok(dtypes.T)", "axisDispatchFits", "axisRowScratchFits"],
516
+ "intermediates": [
517
+ { "id": "rowMax", "dtype": "float32", "shape": "[numel(shapes.x) / dim(shapes.x, axis)]" },
518
+ { "id": "rowSum", "dtype": "float32", "shape": "[numel(shapes.x) / dim(shapes.x, axis)]" }
519
+ ],
520
+ "passes": [
521
+ {
522
+ "id": "reduce_max",
523
+ "name": "LogSoftmax.StridedReduceMax",
524
+ "source": {
525
+ "shader": "softmax-row-stage.wgsl.jinja",
526
+ "inputs": { "stage": "\"max\"", "strided": "true", "xShape": "shapes.x", "axis": "axis" }
527
+ },
528
+ "bindings": "stridedScalarReduce",
529
+ "dispatch": { "workgroups": "numel(shapes.x) / dim(shapes.x, axis)" }
530
+ },
531
+ {
532
+ "id": "exp_sum",
533
+ "name": "LogSoftmax.StridedExpSum",
534
+ "source": {
535
+ "shader": "softmax-row-stage.wgsl.jinja",
536
+ "inputs": { "stage": "\"exp_sum\"", "strided": "true", "xShape": "shapes.x", "axis": "axis" }
537
+ },
538
+ "bindings": "stridedScalarExpSum",
539
+ "dispatch": { "workgroups": "numel(shapes.x) / dim(shapes.x, axis)" }
540
+ },
541
+ {
542
+ "id": "normalize",
543
+ "name": "LogSoftmax.StridedNormalize",
544
+ "source": {
545
+ "shader": "softmax-normalize.wgsl.jinja",
546
+ "inputs": { "strided": "true", "xShape": "shapes.x", "axis": "axis", "op": "\"logsoftmax\"" }
547
+ },
548
+ "bindings": "normalize",
549
+ "dispatch": { "threads": "numel(shapes.x)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
550
+ }
551
+ ]
552
+ },
553
+ {
554
+ "id": "strided_online_capacity_fallback",
555
+ "priority": -1,
556
+ "when": ["stridedAxisOk", "f16Ok(dtypes.T)", "axisThreadDispatchFits"],
557
+ "passes": [
558
+ {
559
+ "id": "main",
560
+ "name": "LogSoftmax.StridedOnlineCapacityFallback",
561
+ "source": {
562
+ "shader": "softmax-strided-online-lane.wgsl.jinja",
563
+ "inputs": { "xShape": "shapes.x", "axis": "axis", "op": "\"logsoftmax\"" }
564
+ },
565
+ "bindings": "stridedOnline",
566
+ "dispatch": { "threads": "axisRows", "workgroupSize": "tunables.WORKGROUP_SIZE" }
567
+ }
568
+ ]
569
+ }
570
+ ]
571
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.LogSoftmax",
3
+ "id": "_ai_onnx_logsoftmax_webgpu_0804fe4",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "rqFaX5XmJwQz0QQ1ET5FRMCuYEMotdcOJR+z6Y57a/0=",
11
+ "manifest.json": "QDbHBdv1drmn3+Mqxdqx24xatM7c38mQxNZrDzDPmM8=",
12
+ "softmax-longrow-normalize.wgsl.jinja": "AamVdoPDaWPQ3sk5loxtcuNu7oftC5J7hsPwLRDA7M0=",
13
+ "softmax-longrow-stats.wgsl.jinja": "K0PI+9cmRgjJsOOcBRyugN0Dm7yxBy0xvyPamEpCgxo=",
14
+ "softmax-normalize.wgsl.jinja": "eekHyttLyBk2X/mG54dUS2kIc5KycR3grH6000pqh1A=",
15
+ "softmax-online-packed-rows.wgsl.jinja": "94Xmz8Mtj/ji7XpIQUpsDdO6uShKJtWknzEI0Er+sT8=",
16
+ "softmax-online.wgsl.jinja": "qL/179d83HTFuyCUkfHqE0MBbtFMOEl8lzAH7aWmUXA=",
17
+ "softmax-row-stage-strided-vec4.wgsl.jinja": "l1byT6xoLl6Q7kSPd/uws6X5NsS2PwlSv2TBMCioVZA=",
18
+ "softmax-row-stage.wgsl.jinja": "dxlM6y02DwXeLeavHDiq70h0NCRZgz+bniCltyEgRX4=",
19
+ "softmax-strided-online-lane.wgsl.jinja": "BTLdB6LamPLNJsNmsllObl/9Y/Cx1EbuwdqeYpcppXg=",
20
+ "softmax-strided-packed4-tail.wgsl.jinja": "z7+TYeybaE/yXds/ODe4wMyosZeIrCtTfUmZsA2eIXo=",
21
+ "test.json": "uvZ0xakkYLGTptboBDjo8khdaQQ2h+QWiu4Z/wLWmfQ="
22
+ }
23
+ },
24
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
25
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.LogSoftmax" }
26
+ }
build/webgpu/softmax-longrow-normalize.wgsl.jinja ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Final normalization pass for long-row split softmax. Each workgroup handles
2
+ // one block of a row and re-reads the input against the completed row maximum
3
+ // and denominator. The epilogue supports softmax and log-softmax.
4
+ {% if T == "f16" %}
5
+ enable f16;
6
+ {% endif %}
7
+ {{ env.wgsl.resourceDeclarations }}
8
+
9
+ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
10
+ const BLOCK_COLS: u32 = {{ tunables.BLOCK_COLS }}u;
11
+ // FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
12
+ // `m - m` finite so an empty lane / all--inf row contributes the exact
13
+ // accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
14
+ // a zero final denominator according to their public semantics. Using -inf
15
+ // here changes +inf-row behavior.
16
+ const FLT_MAX: f32 = 3.4028234663852886e38;
17
+
18
+ fn is_finite_f32(value: f32) -> bool {
19
+ return select(false, value <= FLT_MAX, value >= -FLT_MAX);
20
+ }
21
+
22
+ // x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
23
+ // exactly at the row max. `x - x` on an infinite max is a legal fast-math
24
+ // fold to 0, which would silently turn +inf rows finite — the explicit
25
+ // equality test keeps the NaN propagation of the serial kernels.
26
+ fn shifted_value(value: f32, maxValue: f32) -> f32 {
27
+ let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
28
+ return select(value - maxValue, 0.0, equalFiniteMax);
29
+ }
30
+ // Bit-exact NaN test: `v != v` can be folded to false by fast-math backends,
31
+ // and a constant NaN bit pattern is rejected at WGSL const-evaluation.
32
+ fn is_nan_f32(value: f32) -> bool {
33
+ let bits = bitcast<u32>(value);
34
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
35
+ }
36
+
37
+
38
+ @compute @workgroup_size(WG)
39
+ fn main(@builtin(workgroup_id) wg: vec3<u32>,
40
+ @builtin(local_invocation_id) lid: vec3<u32>) {
41
+ let block = wg.x;
42
+ let row = wg.y;
43
+ let rowBase = row * params.cols;
44
+ let colStart = block * BLOCK_COLS;
45
+ let colEnd = min(params.cols, colStart + BLOCK_COLS);
46
+
47
+ let m = rowMax[row];
48
+ let d = rowSum[row];
49
+ // NaN row (any NaN input, or +inf max) -> all-NaN row, with the quiet NaN
50
+ // derived from the runtime value (a constant NaN bit pattern is rejected at
51
+ // WGSL const-evaluation).
52
+ let rowIsNan = m > FLT_MAX || is_nan_f32(d) || d == 0.0;
53
+ let nanOut = bitcast<f32>(bitcast<u32>(d) | 0x7fc00000u);
54
+ let logD = log(d);
55
+
56
+ for (var col = colStart + lid.x; col < colEnd; col = col + WG) {
57
+ let v = f32(x[rowBase + col]);
58
+ // (x - m) - log(d). x == m gives exactly 0 - log(d) (IEEE subtraction of
59
+ // equal finite values is +0), matching the serial kernel's shift trick.
60
+ var out = shifted_value(v, m) - logD;
61
+ if (rowIsNan) {
62
+ out = nanOut;
63
+ }
64
+ y[rowBase + col] = {{ T }}(out);
65
+ }
66
+ }
build/webgpu/softmax-longrow-stats.wgsl.jinja ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Online-softmax (maximum, sum-exp) statistics for very long contiguous
2
+ // last-axis rows. The statistics are the same for softmax and log-softmax.
3
+ // The block stage assigns one workgroup to each (block, row), merges per-thread
4
+ // online pairs, and writes one partial pair. The row stage assigns one
5
+ // workgroup per row and folds those block partials into rowMax and rowSum.
6
+ {% if source.stage == "block" and T == "f16" %}
7
+ enable f16;
8
+ {% endif %}
9
+ {{ env.wgsl.resourceDeclarations }}
10
+
11
+ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
12
+ {% if source.stage == "block" %}
13
+ const BLOCK_COLS: u32 = {{ tunables.BLOCK_COLS }}u;
14
+ {% endif %}
15
+ // FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
16
+ // `m - m` finite so an empty lane / all--inf row contributes the exact
17
+ // accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
18
+ // a zero final denominator according to their public semantics. Using -inf
19
+ // here changes +inf-row behavior.
20
+ const FLT_MAX: f32 = 3.4028234663852886e38;
21
+
22
+ fn is_finite_f32(value: f32) -> bool {
23
+ return select(false, value <= FLT_MAX, value >= -FLT_MAX);
24
+ }
25
+
26
+ // x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
27
+ // exactly at the row max. `x - x` on an infinite max is a legal fast-math
28
+ // fold to 0, which would silently turn +inf rows finite — the explicit
29
+ // equality test keeps the NaN propagation of the serial kernels.
30
+ fn shifted_value(value: f32, maxValue: f32) -> f32 {
31
+ let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
32
+ return select(value - maxValue, 0.0, equalFiniteMax);
33
+ }
34
+ fn exp_shift(value: f32, maxValue: f32) -> f32 {
35
+ return exp(shifted_value(value, maxValue));
36
+ }
37
+ // Workgroup-cooperative merge of per-thread online-softmax (m, d) partials:
38
+ // mNew = max(m1, m2)
39
+ // dNew = d1 * exp(m1 - mNew) + d2 * exp(m2 - mNew)
40
+ // Both the subgroup and portable barrier-tree engines return the same merged
41
+ // pair to every invocation. Repeated merges require a workgroup barrier between
42
+ // calls before their shared partial storage is reused.
43
+ {% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
44
+ {% if combineSubgroups %}
45
+ // Per-subgroup partials are published into a deterministic slot: the subgroup's
46
+ // ordinal index within the workgroup (lidx / sgSize). The online (m, d) merge
47
+ // is not float-associative, so thread 0 must fold partials in a fixed order.
48
+ // Subgroups partition a workgroup into contiguous ordinal ranges on supported
49
+ // backends, so the ordinal slot is unique per subgroup and every slot in
50
+ // [0, subgroupCount) is written (each subgroup elects one leader).
51
+ // Sized for the worst case of one partial per invocation.
52
+ var<workgroup> partialM: array<f32, WG>;
53
+ var<workgroup> partialD: array<f32, WG>;
54
+ var<workgroup> combinedMD: vec2<f32>;
55
+
56
+ // When the whole workgroup is one subgroup the subgroup reduce already covers
57
+ // it (no barriers, no shared state); otherwise subgroup leaders publish
58
+ // partials through shared memory and thread 0 folds them in ordinal order.
59
+ fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
60
+ let sgM = subgroupMax(m);
61
+ // A lane with no elements contributes d == 0 (exact identity). A +inf
62
+ // element made exp(inf - inf) = NaN stick in that lane's d; a NaN element
63
+ // landed in d via exp(NaN); both survive the merge and are detected by the
64
+ // code after the reduction.
65
+ let sgD = subgroupAdd(d * exp_shift(m, sgM));
66
+ if (sgSize == WG) {
67
+ return vec2<f32>(sgM, sgD);
68
+ }
69
+ let subgroupCount = (WG + sgSize - 1u) / sgSize;
70
+ // Pre-seed every fold slot with the (max, denom) identity. The fold below reads a
71
+ // fixed subgroupCount slots in ordinal order (for determinism), but a slot whose
72
+ // subgroup elects no leader this call — e.g. a fully out-of-window key tile in the
73
+ // flash-attention loop that re-uses this shared memory each iteration — would
74
+ // otherwise read stale shared memory. Identity makes such a slot a no-op.
75
+ // (max identity = -FLT_MAX, denom identity = 0.)
76
+ if (lidx < subgroupCount) {
77
+ partialM[lidx] = -FLT_MAX;
78
+ partialD[lidx] = 0.0;
79
+ }
80
+ workgroupBarrier();
81
+ if (subgroupElect()) {
82
+ let slot = lidx / sgSize;
83
+ partialM[slot] = sgM;
84
+ partialD[slot] = sgD;
85
+ }
86
+ workgroupBarrier();
87
+ if (lidx == 0u) {
88
+ var accM = -FLT_MAX;
89
+ var accD = 0.0;
90
+ for (var i = 0u; i < subgroupCount; i = i + 1u) {
91
+ let mNew = max(accM, partialM[i]);
92
+ accD = accD * exp_shift(accM, mNew) + partialD[i] * exp_shift(partialM[i], mNew);
93
+ accM = mNew;
94
+ }
95
+ combinedMD = vec2<f32>(accM, accD);
96
+ }
97
+ workgroupBarrier();
98
+ return combinedMD;
99
+ }
100
+ {% else %}
101
+ {% set mdStreamed = mdStreams is defined %}
102
+ {% set mdStreams = mdStreams if mdStreams is defined else 1 %}
103
+ {% set mdExtent = "WG" if mdStreams == 1 else "WG * " ~ mdStreams ~ "u" %}
104
+ var<workgroup> partialM: array<f32, {{ mdExtent }}>;
105
+ var<workgroup> partialD: array<f32, {{ mdExtent }}>;
106
+ {% if mdStreamed %}
107
+
108
+ // In-place fold of {{ mdStreams }} streams. The caller stores its per-thread
109
+ // partials into partialM/partialD first and reads the merged pair of stream s
110
+ // from slot s * WG afterwards.
111
+ fn combine_partials_streams(lidx: u32) {
112
+ workgroupBarrier();
113
+ var stride = WG / 2u;
114
+ loop {
115
+ if (stride == 0u) {
116
+ break;
117
+ }
118
+ if (lidx < stride) {
119
+ {% for s in range(mdStreams) %}
120
+ {
121
+ let slot = {{ s }}u * WG + lidx;
122
+ let m1 = partialM[slot];
123
+ let d1 = partialD[slot];
124
+ let m2 = partialM[slot + stride];
125
+ let d2 = partialD[slot + stride];
126
+ let mNew = max(m1, m2);
127
+ partialD[slot] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
128
+ partialM[slot] = mNew;
129
+ }
130
+ {% endfor %}
131
+ }
132
+ workgroupBarrier();
133
+ stride = stride / 2u;
134
+ }
135
+ }
136
+ {% else %}
137
+
138
+ fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
139
+ partialM[lidx] = m;
140
+ partialD[lidx] = d;
141
+ workgroupBarrier();
142
+ var stride = WG / 2u;
143
+ loop {
144
+ if (stride == 0u) {
145
+ break;
146
+ }
147
+ if (lidx < stride) {
148
+ let m1 = partialM[lidx];
149
+ let d1 = partialD[lidx];
150
+ let m2 = partialM[lidx + stride];
151
+ let d2 = partialD[lidx + stride];
152
+ let mNew = max(m1, m2);
153
+ partialD[lidx] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
154
+ partialM[lidx] = mNew;
155
+ }
156
+ workgroupBarrier();
157
+ stride = stride / 2u;
158
+ }
159
+ let merged = vec2<f32>(partialM[0], partialD[0]);
160
+ // Trailing barrier so back-to-back calls cannot race a next call's partial
161
+ // stores against this call's reads of slot 0.
162
+ workgroupBarrier();
163
+ return merged;
164
+ }
165
+ {% endif %}
166
+ {% endif %}
167
+
168
+
169
+ @compute @workgroup_size(WG)
170
+ fn main(@builtin(workgroup_id) wg: vec3<u32>,
171
+ @builtin(local_invocation_id) lid: vec3<u32>) {
172
+ {% if source.stage == "block" %}
173
+ let block = wg.x;
174
+ let row = wg.y;
175
+ let rowBase = row * params.cols;
176
+ let colStart = block * BLOCK_COLS;
177
+ let colEnd = min(params.cols, colStart + BLOCK_COLS);
178
+
179
+ var m = -FLT_MAX;
180
+ var d = 0.0;
181
+ for (var col = colStart + lid.x; col < colEnd; col = col + WG) {
182
+ let v = f32(x[rowBase + col]);
183
+ let mNew = max(m, v);
184
+ d = d * exp_shift(m, mNew) + exp_shift(v, mNew);
185
+ m = mNew;
186
+ }
187
+ {% else %}
188
+ let row = wg.x;
189
+ let base = row * params.blocks;
190
+
191
+ var m = -FLT_MAX;
192
+ var d = 0.0;
193
+ for (var block = lid.x; block < params.blocks; block = block + WG) {
194
+ let bm = blockMax[base + block];
195
+ let bd = blockSum[base + block];
196
+ let mNew = max(m, bm);
197
+ d = d * exp_shift(m, mNew) + bd * exp_shift(bm, mNew);
198
+ m = mNew;
199
+ }
200
+ {% endif %}
201
+
202
+ let merged = combine_partials(m, d, lid.x);
203
+ if (lid.x == 0u) {
204
+ {% if source.stage == "block" %}
205
+ let out = row * params.blocks + block;
206
+ blockMax[out] = merged.x;
207
+ blockSum[out] = merged.y;
208
+ {% else %}
209
+ rowMax[row] = merged.x;
210
+ rowSum[row] = merged.y;
211
+ {% endif %}
212
+ }
213
+ }
build/webgpu/softmax-normalize.wgsl.jinja ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Element-parallel pass 3 of three-pass softmax/log-softmax. Strided-axis mode
2
+ // changes only how an element finds its row. Softmax divides the exponential
3
+ // values already in y by the row sum. Log-softmax rereads x against the finished
4
+ // row statistics and writes (x - rowMax) - log(rowSum), so entries whose
5
+ // exponentials underflow remain finite.
6
+ {% if T == "f16" %}
7
+ enable f16;
8
+ {% endif %}
9
+ {{ env.wgsl.resourceDeclarations }}
10
+ {% if source.strided %}
11
+
12
+ {% set stridedAxisUses = ["row_for_offset"] %}
13
+ // Compiled indexing for row-wise reductions over a non-last axis. A row is one
14
+ // slice along the reduction axis, indexed as outer * INNER_SIZE + inner.
15
+ {% set reduce_size = source.xShape[source.axis] %}
16
+ {% set inner_size = namespace(value=1) %}
17
+ {% for i in range(source.axis + 1, source.xShape | length) %}
18
+ {% set inner_size.value = inner_size.value * source.xShape[i] %}
19
+ {% endfor %}
20
+ {% set outer_size = namespace(value=1) %}
21
+ {% for i in range(source.axis) %}
22
+ {% set outer_size.value = outer_size.value * source.xShape[i] %}
23
+ {% endfor %}
24
+ {% set usesAll = stridedAxisUses is not defined %}
25
+ {% set usesElementOffset = usesAll or "element_offset" in stridedAxisUses %}
26
+ {% set usesRowForOffset = usesAll or "row_for_offset" in stridedAxisUses %}
27
+ {% set usesReduceSize = usesAll or usesElementOffset or "REDUCE_SIZE" in stridedAxisUses %}
28
+ {% set usesInnerSize = usesAll or usesElementOffset or "INNER_SIZE" in stridedAxisUses %}
29
+ {% set usesStridedRows = usesAll or "STRIDED_ROWS" in stridedAxisUses %}
30
+ {% set usesInnerSizeSafe = usesAll or usesElementOffset or usesRowForOffset or "INNER_SIZE_SAFE" in stridedAxisUses %}
31
+ {% set usesAxisStrideSafe = usesAll or usesRowForOffset or "AXIS_STRIDE_SAFE" in stridedAxisUses %}
32
+ {% if usesReduceSize %}
33
+ const REDUCE_SIZE: u32 = {{ reduce_size }}u;
34
+ {% endif %}
35
+ {% if usesInnerSize %}
36
+ const INNER_SIZE: u32 = {{ inner_size.value }}u;
37
+ {% endif %}
38
+ {% if usesStridedRows %}
39
+ // Total one-per-row workgroup count (product of every non-axis dim). Lets the
40
+ // row-wise reduce kernels 2D-fold their dispatch past the maxComputeWorkgroupsPerDimension limit and guard
41
+ // the over-dispatched tail without a runtime `params` uniform.
42
+ const STRIDED_ROWS: u32 = {{ outer_size.value * inner_size.value }}u;
43
+ {% endif %}
44
+ {% if usesInnerSizeSafe %}
45
+ const INNER_SIZE_SAFE: u32 = {{ inner_size.value if inner_size.value > 0 else 1 }}u;
46
+ {% endif %}
47
+ {% if usesAxisStrideSafe %}
48
+ const AXIS_STRIDE_SAFE: u32 = {{ (reduce_size * inner_size.value) if (reduce_size * inner_size.value) > 0 else 1 }}u;
49
+ {% endif %}
50
+
51
+ {% if usesElementOffset %}
52
+ // Flat offset of element k (along the reduce axis) of `row`.
53
+ fn element_offset(row: u32, k: u32) -> u32 {
54
+ let outer = row / INNER_SIZE_SAFE;
55
+ let inner = row % INNER_SIZE_SAFE;
56
+ return outer * REDUCE_SIZE * INNER_SIZE + k * INNER_SIZE + inner;
57
+ }
58
+
59
+ {% endif %}
60
+ {% if usesRowForOffset %}
61
+ // Row that owns the flat element `offset` (inverse of element_offset over
62
+ // the non-axis coordinates).
63
+ fn row_for_offset(offset: u32) -> u32 {
64
+ let outer = offset / AXIS_STRIDE_SAFE;
65
+ let inner = offset % INNER_SIZE_SAFE;
66
+ return outer * INNER_SIZE_SAFE + inner;
67
+ }
68
+ {%- endif -%}
69
+ {% endif %}
70
+ // FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
71
+ // `m - m` finite so an empty lane / all--inf row contributes the exact
72
+ // accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
73
+ // a zero final denominator according to their public semantics. Using -inf
74
+ // here changes +inf-row behavior.
75
+ const FLT_MAX: f32 = 3.4028234663852886e38;
76
+
77
+ fn is_finite_f32(value: f32) -> bool {
78
+ return select(false, value <= FLT_MAX, value >= -FLT_MAX);
79
+ }
80
+
81
+ // x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
82
+ // exactly at the row max. `x - x` on an infinite max is a legal fast-math
83
+ // fold to 0, which would silently turn +inf rows finite — the explicit
84
+ // equality test keeps the NaN propagation of the serial kernels.
85
+ fn shifted_value(value: f32, maxValue: f32) -> f32 {
86
+ let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
87
+ return select(value - maxValue, 0.0, equalFiniteMax);
88
+ }
89
+ // Bit-exact NaN test: `v != v` can be folded to false by fast-math backends,
90
+ // and a constant NaN bit pattern is rejected at WGSL const-evaluation.
91
+ fn is_nan_f32(value: f32) -> bool {
92
+ let bits = bitcast<u32>(value);
93
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
94
+ }
95
+
96
+
97
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
98
+ fn main(@builtin(global_invocation_id) gid : vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
99
+ // 2D-folded flat index: gid.y carries the high bits past the
100
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
101
+ let i = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
102
+ if (i >= params.count) {
103
+ return;
104
+ }
105
+ {% if source.strided %}
106
+ let row = row_for_offset(i);
107
+ {% else %}
108
+ let row = i / params.cols;
109
+ {% endif %}
110
+ let m = rowMax[row];
111
+ let d = rowSum[row];
112
+ // NaN row (any NaN input, or +inf max) -> all-NaN row, quiet NaN derived from
113
+ // the runtime value (a constant NaN bit pattern is rejected at const-eval).
114
+ let rowIsNan = m > FLT_MAX || is_nan_f32(d) || d == 0.0;
115
+ let nanOut = bitcast<f32>(bitcast<u32>(d) | 0x7fc00000u);
116
+ // (x - m) - log(d). x == m gives exactly 0 - log(d) (IEEE subtraction of equal
117
+ // finite values is +0), matching the serial kernel's shift trick.
118
+ var out = shifted_value(f32(x[i]), m) - log(d);
119
+ if (rowIsNan) {
120
+ out = nanOut;
121
+ }
122
+ y[i] = {{ T }}(out);
123
+ }
build/webgpu/softmax-online-packed-rows.wgsl.jinja ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Portable many-short-row online softmax / log-softmax. A 256-thread
2
+ // workgroup is partitioned into independent fixed-width row groups (normally
3
+ // 32 rows x 8 vec4 lanes). This preserves fully coalesced 32-column traffic
4
+ // while one workgroup covers many short rows.
5
+ {% if usesF16 %}
6
+ enable f16;
7
+ {% endif %}
8
+ {{ env.wgsl.resourceDeclarations }}
9
+
10
+ const LANES_PER_ROW: u32 = {{ source.lanesPerRow }}u;
11
+ const ROWS_PER_WORKGROUP: u32 = {{ source.rowsPerWorkgroup }}u;
12
+ const WG: u32 = LANES_PER_ROW * ROWS_PER_WORKGROUP;
13
+
14
+ // FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
15
+ // `m - m` finite so an empty lane / all--inf row contributes the exact
16
+ // accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
17
+ // a zero final denominator according to their public semantics. Using -inf
18
+ // here changes +inf-row behavior.
19
+ const FLT_MAX: f32 = 3.4028234663852886e38;
20
+
21
+ fn is_finite_f32(value: f32) -> bool {
22
+ return select(false, value <= FLT_MAX, value >= -FLT_MAX);
23
+ }
24
+
25
+ // x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
26
+ // exactly at the row max. `x - x` on an infinite max is a legal fast-math
27
+ // fold to 0, which would silently turn +inf rows finite — the explicit
28
+ // equality test keeps the NaN propagation of the serial kernels.
29
+ fn shifted_value(value: f32, maxValue: f32) -> f32 {
30
+ let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
31
+ return select(value - maxValue, 0.0, equalFiniteMax);
32
+ }
33
+ fn exp_shift(value: f32, maxValue: f32) -> f32 {
34
+ return exp(shifted_value(value, maxValue));
35
+ }
36
+ // Bit-exact NaN test: `v != v` can be folded to false by fast-math backends,
37
+ // and a constant NaN bit pattern is rejected at WGSL const-evaluation.
38
+ fn is_nan_f32(value: f32) -> bool {
39
+ let bits = bitcast<u32>(value);
40
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
41
+ }
42
+
43
+
44
+ // Each logical row owns one contiguous LANES_PER_ROW slice. All reductions
45
+ // are workgroup-portable; no subgroup width or subgroup feature is assumed.
46
+ var<workgroup> partialM: array<f32, WG>;
47
+ var<workgroup> partialD: array<f32, WG>;
48
+
49
+ @compute @workgroup_size({{ source.lanesPerRow * source.rowsPerWorkgroup }})
50
+ fn main(
51
+ @builtin(workgroup_id) wid: vec3<u32>,
52
+ @builtin(num_workgroups) nwg: vec3<u32>,
53
+ @builtin(local_invocation_id) lid: vec3<u32>,
54
+ ) {
55
+ let lane = lid.x % LANES_PER_ROW;
56
+ let localRow = lid.x / LANES_PER_ROW;
57
+ let partialBase = localRow * LANES_PER_ROW;
58
+ let rowGroups = (params.rows + ROWS_PER_WORKGROUP - 1u) / ROWS_PER_WORKGROUP;
59
+
60
+ // A clamped grid-stride dispatch avoids rectangular over-dispatch once the
61
+ // row-group count exceeds one dispatch dimension while still covering every
62
+ // row of arbitrarily tall tensors.
63
+ for (var rowGroup = wid.x; rowGroup < rowGroups; rowGroup += nwg.x) {
64
+ let row = rowGroup * ROWS_PER_WORKGROUP + localRow;
65
+ let validRow = row < params.rows;
66
+
67
+ var m = -FLT_MAX;
68
+ var d = 0.0;
69
+ if (validRow) {
70
+ let rowBase = row * params.vecCols;
71
+ for (var c = lane; c < params.vecCols; c += LANES_PER_ROW) {
72
+ let v = vec4<f32>(x[rowBase + c]);
73
+ let mNew = max(m, max(max(v.x, v.y), max(v.z, v.w)));
74
+ d = d * exp_shift(m, mNew)
75
+ + exp_shift(v.x, mNew) + exp_shift(v.y, mNew)
76
+ + exp_shift(v.z, mNew) + exp_shift(v.w, mNew);
77
+ m = mNew;
78
+ }
79
+ }
80
+
81
+ let partialIndex = partialBase + lane;
82
+ partialM[partialIndex] = m;
83
+ partialD[partialIndex] = d;
84
+ workgroupBarrier();
85
+
86
+ var stride = LANES_PER_ROW / 2u;
87
+ loop {
88
+ if (stride == 0u) {
89
+ break;
90
+ }
91
+ if (lane < stride) {
92
+ let left = partialBase + lane;
93
+ let right = left + stride;
94
+ let m1 = partialM[left];
95
+ let d1 = partialD[left];
96
+ let m2 = partialM[right];
97
+ let d2 = partialD[right];
98
+ let mNew = max(m1, m2);
99
+ partialD[left] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
100
+ partialM[left] = mNew;
101
+ }
102
+ workgroupBarrier();
103
+ stride /= 2u;
104
+ }
105
+
106
+ // The last reduction barrier publishes slot zero to every lane. Copy the
107
+ // result to private values before any lane can start the next row group.
108
+ let finalM = partialM[partialBase];
109
+ let finalD = partialD[partialBase];
110
+ let dBits = bitcast<u32>(finalD);
111
+ let rowIsNan = finalM > FLT_MAX || is_nan_f32(finalD) || finalD == 0.0;
112
+ let nanOut = bitcast<f32>(dBits | 0x7fc00000u);
113
+
114
+ if (validRow) {
115
+ let rowBase = row * params.vecCols;
116
+ let logD = log(finalD);
117
+ for (var c = lane; c < params.vecCols; c += LANES_PER_ROW) {
118
+ let v = vec4<f32>(x[rowBase + c]);
119
+ var o = vec4<f32>(
120
+ shifted_value(v.x, finalM),
121
+ shifted_value(v.y, finalM),
122
+ shifted_value(v.z, finalM),
123
+ shifted_value(v.w, finalM)) - logD;
124
+ if (rowIsNan) {
125
+ o = vec4<f32>(nanOut);
126
+ }
127
+ y[rowBase + c] = vec4<{{ scalar }}>(o);
128
+ }
129
+ }
130
+ }
131
+ }
build/webgpu/softmax-online.wgsl.jinja ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {% if combineSubgroups %}
5
+ enable subgroups;
6
+ {% endif %}
7
+ {{ env.wgsl.resourceDeclarations }}
8
+
9
+ // Workgroup-level online softmax / log-softmax over one contiguous last-axis
10
+ // row per workgroup. Each thread keeps an online (max, denom) pair over its
11
+ // strided chunk, partials merge with the online-softmax rule (see
12
+ // the shared online-softmax combiner) and a fused second sweep writes
13
+ // the result. The epilogue is either softmax or log-softmax, and vec4 row access
14
+ // is used when rows are vectorized.
15
+ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
16
+ // FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
17
+ // `m - m` finite so an empty lane / all--inf row contributes the exact
18
+ // accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
19
+ // a zero final denominator according to their public semantics. Using -inf
20
+ // here changes +inf-row behavior.
21
+ const FLT_MAX: f32 = 3.4028234663852886e38;
22
+
23
+ fn is_finite_f32(value: f32) -> bool {
24
+ return select(false, value <= FLT_MAX, value >= -FLT_MAX);
25
+ }
26
+
27
+ // x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
28
+ // exactly at the row max. `x - x` on an infinite max is a legal fast-math
29
+ // fold to 0, which would silently turn +inf rows finite — the explicit
30
+ // equality test keeps the NaN propagation of the serial kernels.
31
+ fn shifted_value(value: f32, maxValue: f32) -> f32 {
32
+ let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
33
+ return select(value - maxValue, 0.0, equalFiniteMax);
34
+ }
35
+ fn exp_shift(value: f32, maxValue: f32) -> f32 {
36
+ return exp(shifted_value(value, maxValue));
37
+ }
38
+ // Bit-exact NaN test: `v != v` can be folded to false by fast-math backends,
39
+ // and a constant NaN bit pattern is rejected at WGSL const-evaluation.
40
+ fn is_nan_f32(value: f32) -> bool {
41
+ let bits = bitcast<u32>(value);
42
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
43
+ }
44
+
45
+ // Workgroup-cooperative merge of per-thread online-softmax (m, d) partials:
46
+ // mNew = max(m1, m2)
47
+ // dNew = d1 * exp(m1 - mNew) + d2 * exp(m2 - mNew)
48
+ // Both the subgroup and portable barrier-tree engines return the same merged
49
+ // pair to every invocation. Repeated merges require a workgroup barrier between
50
+ // calls before their shared partial storage is reused.
51
+ {% set combineSubgroups = combineSubgroups is defined and combineSubgroups %}
52
+ {% if combineSubgroups %}
53
+ // Per-subgroup partials are published into a deterministic slot: the subgroup's
54
+ // ordinal index within the workgroup (lidx / sgSize). The online (m, d) merge
55
+ // is not float-associative, so thread 0 must fold partials in a fixed order.
56
+ // Subgroups partition a workgroup into contiguous ordinal ranges on supported
57
+ // backends, so the ordinal slot is unique per subgroup and every slot in
58
+ // [0, subgroupCount) is written (each subgroup elects one leader).
59
+ // Sized for the worst case of one partial per invocation.
60
+ var<workgroup> partialM: array<f32, WG>;
61
+ var<workgroup> partialD: array<f32, WG>;
62
+ var<workgroup> combinedMD: vec2<f32>;
63
+
64
+ // When the whole workgroup is one subgroup the subgroup reduce already covers
65
+ // it (no barriers, no shared state); otherwise subgroup leaders publish
66
+ // partials through shared memory and thread 0 folds them in ordinal order.
67
+ fn combine_partials(m: f32, d: f32, lidx: u32, sgSize: u32) -> vec2<f32> {
68
+ let sgM = subgroupMax(m);
69
+ // A lane with no elements contributes d == 0 (exact identity). A +inf
70
+ // element made exp(inf - inf) = NaN stick in that lane's d; a NaN element
71
+ // landed in d via exp(NaN); both survive the merge and are detected by the
72
+ // code after the reduction.
73
+ let sgD = subgroupAdd(d * exp_shift(m, sgM));
74
+ if (sgSize == WG) {
75
+ return vec2<f32>(sgM, sgD);
76
+ }
77
+ let subgroupCount = (WG + sgSize - 1u) / sgSize;
78
+ // Pre-seed every fold slot with the (max, denom) identity. The fold below reads a
79
+ // fixed subgroupCount slots in ordinal order (for determinism), but a slot whose
80
+ // subgroup elects no leader this call — e.g. a fully out-of-window key tile in the
81
+ // flash-attention loop that re-uses this shared memory each iteration — would
82
+ // otherwise read stale shared memory. Identity makes such a slot a no-op.
83
+ // (max identity = -FLT_MAX, denom identity = 0.)
84
+ if (lidx < subgroupCount) {
85
+ partialM[lidx] = -FLT_MAX;
86
+ partialD[lidx] = 0.0;
87
+ }
88
+ workgroupBarrier();
89
+ if (subgroupElect()) {
90
+ let slot = lidx / sgSize;
91
+ partialM[slot] = sgM;
92
+ partialD[slot] = sgD;
93
+ }
94
+ workgroupBarrier();
95
+ if (lidx == 0u) {
96
+ var accM = -FLT_MAX;
97
+ var accD = 0.0;
98
+ for (var i = 0u; i < subgroupCount; i = i + 1u) {
99
+ let mNew = max(accM, partialM[i]);
100
+ accD = accD * exp_shift(accM, mNew) + partialD[i] * exp_shift(partialM[i], mNew);
101
+ accM = mNew;
102
+ }
103
+ combinedMD = vec2<f32>(accM, accD);
104
+ }
105
+ workgroupBarrier();
106
+ return combinedMD;
107
+ }
108
+ {% else %}
109
+ {% set mdStreamed = mdStreams is defined %}
110
+ {% set mdStreams = mdStreams if mdStreams is defined else 1 %}
111
+ {% set mdExtent = "WG" if mdStreams == 1 else "WG * " ~ mdStreams ~ "u" %}
112
+ var<workgroup> partialM: array<f32, {{ mdExtent }}>;
113
+ var<workgroup> partialD: array<f32, {{ mdExtent }}>;
114
+ {% if mdStreamed %}
115
+
116
+ // In-place fold of {{ mdStreams }} streams. The caller stores its per-thread
117
+ // partials into partialM/partialD first and reads the merged pair of stream s
118
+ // from slot s * WG afterwards.
119
+ fn combine_partials_streams(lidx: u32) {
120
+ workgroupBarrier();
121
+ var stride = WG / 2u;
122
+ loop {
123
+ if (stride == 0u) {
124
+ break;
125
+ }
126
+ if (lidx < stride) {
127
+ {% for s in range(mdStreams) %}
128
+ {
129
+ let slot = {{ s }}u * WG + lidx;
130
+ let m1 = partialM[slot];
131
+ let d1 = partialD[slot];
132
+ let m2 = partialM[slot + stride];
133
+ let d2 = partialD[slot + stride];
134
+ let mNew = max(m1, m2);
135
+ partialD[slot] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
136
+ partialM[slot] = mNew;
137
+ }
138
+ {% endfor %}
139
+ }
140
+ workgroupBarrier();
141
+ stride = stride / 2u;
142
+ }
143
+ }
144
+ {% else %}
145
+
146
+ fn combine_partials(m: f32, d: f32, lidx: u32) -> vec2<f32> {
147
+ partialM[lidx] = m;
148
+ partialD[lidx] = d;
149
+ workgroupBarrier();
150
+ var stride = WG / 2u;
151
+ loop {
152
+ if (stride == 0u) {
153
+ break;
154
+ }
155
+ if (lidx < stride) {
156
+ let m1 = partialM[lidx];
157
+ let d1 = partialD[lidx];
158
+ let m2 = partialM[lidx + stride];
159
+ let d2 = partialD[lidx + stride];
160
+ let mNew = max(m1, m2);
161
+ partialD[lidx] = d1 * exp_shift(m1, mNew) + d2 * exp_shift(m2, mNew);
162
+ partialM[lidx] = mNew;
163
+ }
164
+ workgroupBarrier();
165
+ stride = stride / 2u;
166
+ }
167
+ let merged = vec2<f32>(partialM[0], partialD[0]);
168
+ // Trailing barrier so back-to-back calls cannot race a next call's partial
169
+ // stores against this call's reads of slot 0.
170
+ workgroupBarrier();
171
+ return merged;
172
+ }
173
+ {% endif %}
174
+ {% endif %}
175
+
176
+
177
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
178
+ fn main(@builtin(workgroup_id) wid: vec3<u32>,
179
+ @builtin(num_workgroups) nwg: vec3<u32>,
180
+ @builtin(local_invocation_id) lid: vec3<u32>{% if combineSubgroups %},
181
+ @builtin(subgroup_size) sgSize: u32{% endif %}) {
182
+ // 2D-folded row index: wid.y carries the high bits past the
183
+ // maxComputeWorkgroupsPerDimension dispatch limit (rows > maxComputeWorkgroupsPerDimension).
184
+ let row = wid.x + wid.y * nwg.x;
185
+ if (row >= params.rows) {
186
+ return;
187
+ }
188
+ {% if source.useVec4 %}
189
+ let rowBase = row * params.vecCols;
190
+ {% else %}
191
+ let rowBase = row * params.cols;
192
+ {% endif %}
193
+
194
+ // Thread-local online (m, d) over a strided chunk; f32 accumulation.
195
+ var m = -FLT_MAX;
196
+ var d = 0.0;
197
+ {% if source.useVec4 %}
198
+ for (var c = lid.x; c < params.vecCols; c = c + WG) {
199
+ let v = vec4<f32>(x[rowBase + c]);
200
+ let mNew = max(m, max(max(v.x, v.y), max(v.z, v.w)));
201
+ d = d * exp_shift(m, mNew)
202
+ + exp_shift(v.x, mNew) + exp_shift(v.y, mNew) + exp_shift(v.z, mNew) + exp_shift(v.w, mNew);
203
+ m = mNew;
204
+ }
205
+ {% else %}
206
+ for (var c = lid.x; c < params.cols; c = c + WG) {
207
+ let v = f32(x[rowBase + c]);
208
+ let mNew = max(m, v);
209
+ d = d * exp_shift(m, mNew) + exp_shift(v, mNew);
210
+ m = mNew;
211
+ }
212
+ {% endif %}
213
+
214
+ let rowMD = combine_partials(m, d, lid.x{% if combineSubgroups %}, sgSize{% endif %});
215
+ let finalM = rowMD.x;
216
+ let finalD = rowMD.y;
217
+
218
+ // Runtime-derived quiet NaN + bit-exact NaN test, as in the 1-pass kernel:
219
+ // `m - m` (Inf - Inf) and `d != d` are both legal fast-math folds (to 0 and
220
+ // false), and a constant NaN bit pattern is rejected at WGSL
221
+ // const-evaluation.
222
+ let dBits = bitcast<u32>(finalD);
223
+ let rowIsNan = finalM > FLT_MAX || is_nan_f32(finalD) || finalD == 0.0;
224
+ let nanOut = bitcast<f32>(dBits | 0x7fc00000u);
225
+
226
+ // (x - m) - log(d). x == m gives exactly 0 - log(d) (IEEE subtraction of
227
+ // equal finite values is +0), matching the serial kernel's shift trick.
228
+ let logD = log(finalD);
229
+ {% if source.useVec4 %}
230
+ for (var c = lid.x; c < params.vecCols; c = c + WG) {
231
+ let v = vec4<f32>(x[rowBase + c]);
232
+ var o = vec4<f32>(
233
+ shifted_value(v.x, finalM),
234
+ shifted_value(v.y, finalM),
235
+ shifted_value(v.z, finalM),
236
+ shifted_value(v.w, finalM)) - logD;
237
+ if (rowIsNan) {
238
+ o = vec4<f32>(nanOut);
239
+ }
240
+ y[rowBase + c] = vec4<{{ scalar }}>(o);
241
+ }
242
+ {% else %}
243
+ for (var c = lid.x; c < params.cols; c = c + WG) {
244
+ let v = f32(x[rowBase + c]);
245
+ var o = shifted_value(v, finalM) - logD;
246
+ if (rowIsNan) {
247
+ o = nanOut;
248
+ }
249
+ y[rowBase + c] = {{ scalar }}(o);
250
+ }
251
+ {% endif %}
252
+ }
build/webgpu/softmax-row-stage-strided-vec4.wgsl.jinja ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Vec4-over-inner specialization for passes 1 and 2 of strided three-pass
2
+ // softmax. A strided row reduces REDUCE_SIZE elements at INNER_SIZE stride, so
3
+ // adjacent scalar threads would read non-coalesced locations. When INNER_SIZE
4
+ // is divisible by four, four adjacent inner positions remain contiguous at
5
+ // every axis coordinate. One workgroup therefore handles four rows packed as a
6
+ // vec4: one coalesced 128-bit access per axis step through one reduction tree.
7
+ // rowMax and rowSum use the same bytes the scalar normalize pass reads.
8
+ {{ env.wgsl.resourceDeclarations }}
9
+
10
+ const WGS: u32 = {{ tunables.WORKGROUP_SIZE }}u;
11
+ {% set stridedAxisUses = ["REDUCE_SIZE", "INNER_SIZE", "STRIDED_ROWS"] %}
12
+ // Compiled indexing for row-wise reductions over a non-last axis. A row is one
13
+ // slice along the reduction axis, indexed as outer * INNER_SIZE + inner.
14
+ {% set reduce_size = source.xShape[source.axis] %}
15
+ {% set inner_size = namespace(value=1) %}
16
+ {% for i in range(source.axis + 1, source.xShape | length) %}
17
+ {% set inner_size.value = inner_size.value * source.xShape[i] %}
18
+ {% endfor %}
19
+ {% set outer_size = namespace(value=1) %}
20
+ {% for i in range(source.axis) %}
21
+ {% set outer_size.value = outer_size.value * source.xShape[i] %}
22
+ {% endfor %}
23
+ {% set usesAll = stridedAxisUses is not defined %}
24
+ {% set usesElementOffset = usesAll or "element_offset" in stridedAxisUses %}
25
+ {% set usesRowForOffset = usesAll or "row_for_offset" in stridedAxisUses %}
26
+ {% set usesReduceSize = usesAll or usesElementOffset or "REDUCE_SIZE" in stridedAxisUses %}
27
+ {% set usesInnerSize = usesAll or usesElementOffset or "INNER_SIZE" in stridedAxisUses %}
28
+ {% set usesStridedRows = usesAll or "STRIDED_ROWS" in stridedAxisUses %}
29
+ {% set usesInnerSizeSafe = usesAll or usesElementOffset or usesRowForOffset or "INNER_SIZE_SAFE" in stridedAxisUses %}
30
+ {% set usesAxisStrideSafe = usesAll or usesRowForOffset or "AXIS_STRIDE_SAFE" in stridedAxisUses %}
31
+ {% if usesReduceSize %}
32
+ const REDUCE_SIZE: u32 = {{ reduce_size }}u;
33
+ {% endif %}
34
+ {% if usesInnerSize %}
35
+ const INNER_SIZE: u32 = {{ inner_size.value }}u;
36
+ {% endif %}
37
+ {% if usesStridedRows %}
38
+ // Total one-per-row workgroup count (product of every non-axis dim). Lets the
39
+ // row-wise reduce kernels 2D-fold their dispatch past the maxComputeWorkgroupsPerDimension limit and guard
40
+ // the over-dispatched tail without a runtime `params` uniform.
41
+ const STRIDED_ROWS: u32 = {{ outer_size.value * inner_size.value }}u;
42
+ {% endif %}
43
+ {% if usesInnerSizeSafe %}
44
+ const INNER_SIZE_SAFE: u32 = {{ inner_size.value if inner_size.value > 0 else 1 }}u;
45
+ {% endif %}
46
+ {% if usesAxisStrideSafe %}
47
+ const AXIS_STRIDE_SAFE: u32 = {{ (reduce_size * inner_size.value) if (reduce_size * inner_size.value) > 0 else 1 }}u;
48
+ {% endif %}
49
+
50
+ {% if usesElementOffset %}
51
+ // Flat offset of element k (along the reduce axis) of `row`.
52
+ fn element_offset(row: u32, k: u32) -> u32 {
53
+ let outer = row / INNER_SIZE_SAFE;
54
+ let inner = row % INNER_SIZE_SAFE;
55
+ return outer * REDUCE_SIZE * INNER_SIZE + k * INNER_SIZE + inner;
56
+ }
57
+
58
+ {% endif %}
59
+ {% if usesRowForOffset %}
60
+ // Row that owns the flat element `offset` (inverse of element_offset over
61
+ // the non-axis coordinates).
62
+ fn row_for_offset(offset: u32) -> u32 {
63
+ let outer = offset / AXIS_STRIDE_SAFE;
64
+ let inner = offset % INNER_SIZE_SAFE;
65
+ return outer * INNER_SIZE_SAFE + inner;
66
+ }
67
+ {%- endif -%}
68
+ {% if source.stage == "max" %}
69
+ // max(1u,..) so an empty strided tensor (INNER_SIZE 0) still compiles — the kernel
70
+ // returns before any divide (VEC4_ROWS 0 -> guard), this only avoids a const `/0`.
71
+ const INNER4: u32 = max(1u, INNER_SIZE / 4u); // vec4 groups of inner positions per outer
72
+ const STRIDE4: u32 = INNER_SIZE / 4u; // vec4 stride along the reduce axis
73
+ const VEC4_ROWS: u32 = STRIDED_ROWS / 4u; // = outer_size * INNER4
74
+ {% else %}
75
+ // max(1u,..) so an empty strided tensor (INNER_SIZE 0) still compiles (kernel
76
+ // returns before any divide; only avoids a const `/0`).
77
+ const INNER4: u32 = max(1u, INNER_SIZE / 4u);
78
+ const STRIDE4: u32 = INNER_SIZE / 4u;
79
+ const VEC4_ROWS: u32 = STRIDED_ROWS / 4u;
80
+ {% endif %}
81
+
82
+ var<workgroup> partial: array<vec4<f32>, WGS>;
83
+ {% set reduce_fn = "reduce_max_vec4" if source.stage == "max" else "reduce_add_vec4" %}
84
+ fn {{ reduce_fn }}(value: vec4<f32>, tid: u32) -> vec4<f32> {
85
+ partial[tid] = value;
86
+ workgroupBarrier();
87
+ var stride: u32 = WGS / 2u;
88
+ loop {
89
+ if (stride == 0u) { break; }
90
+ {% if source.stage == "max" %}
91
+ if (tid < stride) { partial[tid] = max(partial[tid], partial[tid + stride]); }
92
+ {% else %}
93
+ if (tid < stride) { partial[tid] = partial[tid] + partial[tid + stride]; }
94
+ {% endif %}
95
+ stride = stride / 2u;
96
+ workgroupBarrier();
97
+ }
98
+ return partial[0];
99
+ }
100
+
101
+ @compute @workgroup_size(WGS)
102
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>,
103
+ @builtin(num_workgroups) nwg: vec3<u32>) {
104
+ let rg = wg.x + wg.y * nwg.x; // vec4-row-group index
105
+ if (rg >= VEC4_ROWS) { return; }
106
+ let outer = rg / INNER4;
107
+ let inner_group = rg % INNER4;
108
+ {% if source.stage == "max" %}
109
+ // vec4 index of element k=0 for this group (offset is a multiple of 4 since
110
+ // INNER_SIZE % 4 == 0 and inner_group*4 is too).
111
+ {% endif %}
112
+ let base4 = (outer * REDUCE_SIZE * INNER_SIZE + inner_group * 4u) / 4u;
113
+
114
+ {% if source.stage == "max" %}
115
+ var acc = vec4<f32>(-3.4028234663852886e38);
116
+ for (var k = lid.x; k < REDUCE_SIZE; k = k + WGS) {
117
+ acc = max(acc, x[base4 + k * STRIDE4]);
118
+ }
119
+ let rowM = reduce_max_vec4(acc, lid.x);
120
+ if (lid.x == 0u) { rowMax[rg] = rowM; }
121
+ {% else %}
122
+ let m = rowMax[rg];
123
+ // +Inf row max -> NaN row (ONNX). Derive the quiet NaN per-component from the
124
+ // runtime bits (a constant NaN pattern is rejected at const-eval); m - m folds
125
+ // to 0 for finite m so the exp arg stays correct on the non-Inf lanes.
126
+ let nanv = bitcast<vec4<f32>>(bitcast<vec4<u32>>(m) | vec4<u32>(0x7fc00000u));
127
+ let isInf = m > vec4<f32>(3.4028234663852886e38);
128
+ var acc = vec4<f32>(0.0);
129
+ for (var k = lid.x; k < REDUCE_SIZE; k = k + WGS) {
130
+ let idx = base4 + k * STRIDE4;
131
+ let e = select(exp(x[idx] - m), nanv, isInf);
132
+ y[idx] = e;
133
+ acc = acc + e;
134
+ }
135
+ let rowSumValue = reduce_add_vec4(acc, lid.x);
136
+ if (lid.x == 0u) { rowSum[rg] = rowSumValue; }
137
+ {% endif %}
138
+ }
build/webgpu/softmax-row-stage.wgsl.jinja ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // First and second passes of the three-pass softmax, with one workgroup per
2
+ // row. The max stage reduces the row maximum. The exp_sum stage writes
3
+ // exp(x - rowMax) and reduces the row denominator.
4
+ {% if T == "f16" %}
5
+ enable f16;
6
+ {% endif %}
7
+ {{ env.wgsl.resourceDeclarations }}
8
+
9
+ const WGS: u32 = {{ tunables.WORKGROUP_SIZE }}u;
10
+ {% if source.strided %}
11
+ {% set stridedAxisUses = ["STRIDED_ROWS", "element_offset"] %}
12
+ // Compiled indexing for row-wise reductions over a non-last axis. A row is one
13
+ // slice along the reduction axis, indexed as outer * INNER_SIZE + inner.
14
+ {% set reduce_size = source.xShape[source.axis] %}
15
+ {% set inner_size = namespace(value=1) %}
16
+ {% for i in range(source.axis + 1, source.xShape | length) %}
17
+ {% set inner_size.value = inner_size.value * source.xShape[i] %}
18
+ {% endfor %}
19
+ {% set outer_size = namespace(value=1) %}
20
+ {% for i in range(source.axis) %}
21
+ {% set outer_size.value = outer_size.value * source.xShape[i] %}
22
+ {% endfor %}
23
+ {% set usesAll = stridedAxisUses is not defined %}
24
+ {% set usesElementOffset = usesAll or "element_offset" in stridedAxisUses %}
25
+ {% set usesRowForOffset = usesAll or "row_for_offset" in stridedAxisUses %}
26
+ {% set usesReduceSize = usesAll or usesElementOffset or "REDUCE_SIZE" in stridedAxisUses %}
27
+ {% set usesInnerSize = usesAll or usesElementOffset or "INNER_SIZE" in stridedAxisUses %}
28
+ {% set usesStridedRows = usesAll or "STRIDED_ROWS" in stridedAxisUses %}
29
+ {% set usesInnerSizeSafe = usesAll or usesElementOffset or usesRowForOffset or "INNER_SIZE_SAFE" in stridedAxisUses %}
30
+ {% set usesAxisStrideSafe = usesAll or usesRowForOffset or "AXIS_STRIDE_SAFE" in stridedAxisUses %}
31
+ {% if usesReduceSize %}
32
+ const REDUCE_SIZE: u32 = {{ reduce_size }}u;
33
+ {% endif %}
34
+ {% if usesInnerSize %}
35
+ const INNER_SIZE: u32 = {{ inner_size.value }}u;
36
+ {% endif %}
37
+ {% if usesStridedRows %}
38
+ // Total one-per-row workgroup count (product of every non-axis dim). Lets the
39
+ // row-wise reduce kernels 2D-fold their dispatch past the maxComputeWorkgroupsPerDimension limit and guard
40
+ // the over-dispatched tail without a runtime `params` uniform.
41
+ const STRIDED_ROWS: u32 = {{ outer_size.value * inner_size.value }}u;
42
+ {% endif %}
43
+ {% if usesInnerSizeSafe %}
44
+ const INNER_SIZE_SAFE: u32 = {{ inner_size.value if inner_size.value > 0 else 1 }}u;
45
+ {% endif %}
46
+ {% if usesAxisStrideSafe %}
47
+ const AXIS_STRIDE_SAFE: u32 = {{ (reduce_size * inner_size.value) if (reduce_size * inner_size.value) > 0 else 1 }}u;
48
+ {% endif %}
49
+
50
+ {% if usesElementOffset %}
51
+ // Flat offset of element k (along the reduce axis) of `row`.
52
+ fn element_offset(row: u32, k: u32) -> u32 {
53
+ let outer = row / INNER_SIZE_SAFE;
54
+ let inner = row % INNER_SIZE_SAFE;
55
+ return outer * REDUCE_SIZE * INNER_SIZE + k * INNER_SIZE + inner;
56
+ }
57
+
58
+ {% endif %}
59
+ {% if usesRowForOffset %}
60
+ // Row that owns the flat element `offset` (inverse of element_offset over
61
+ // the non-axis coordinates).
62
+ fn row_for_offset(offset: u32) -> u32 {
63
+ let outer = offset / AXIS_STRIDE_SAFE;
64
+ let inner = offset % INNER_SIZE_SAFE;
65
+ return outer * INNER_SIZE_SAFE + inner;
66
+ }
67
+ {%- endif -%}
68
+ {% endif %}
69
+ {% macro wgsl_tree_reduce_f32(name, mode, buffer="partial", wg="WG", trailingBarrier=true) %}
70
+ fn {{ name }}(value: f32, tid: u32) -> f32 {
71
+ {{ buffer }}[tid] = value;
72
+ workgroupBarrier();
73
+ // Ceil-halving keeps every lane when the workgroup size is not a power of
74
+ // two. For even n this matches the power-of-two tree order; for odd n, lanes
75
+ // [0, n-half) fold the upper tail while the middle lane carries forward.
76
+ var n: u32 = {{ wg }};
77
+ loop {
78
+ let half = (n + 1u) / 2u;
79
+ if (tid < n - half) {
80
+ {% if mode == "max" %}
81
+ {{ buffer }}[tid] = max({{ buffer }}[tid], {{ buffer }}[tid + half]);
82
+ {% else %}
83
+ {{ buffer }}[tid] = {{ buffer }}[tid] + {{ buffer }}[tid + half];
84
+ {% endif %}
85
+ }
86
+ workgroupBarrier();
87
+ n = half;
88
+ if (n == 1u) {
89
+ break;
90
+ }
91
+ }
92
+ // The default trailing barrier makes this helper safe for back-to-back calls: every lane reads
93
+ // slot 0 here, so the next call's first store must not run until all lanes have read it.
94
+ // `trailingBarrier=false` is safe only when the buffer is never written again before kernel exit.
95
+ let reduced = {{ buffer }}[0];
96
+ {% if trailingBarrier %}
97
+ workgroupBarrier();
98
+ {% endif %}
99
+ return reduced;
100
+ }
101
+ {% endmacro %}
102
+
103
+ var<workgroup> partial: array<f32, WGS>;
104
+ {% if source.stage == "max" %}
105
+ {{ wgsl_tree_reduce_f32("reduce_max_f32", "max", "partial", "WGS") }}
106
+ {% else %}
107
+ {{ wgsl_tree_reduce_f32("reduce_add_f32", "add", "partial", "WGS") }}
108
+ {% endif %}
109
+ {% set count = "REDUCE_SIZE" if source.strided else "params.cols" %}
110
+ {% set offset_of = "element_offset(row, k)" if source.strided else "row * params.cols + k" %}
111
+
112
+ @compute @workgroup_size(WGS)
113
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>,
114
+ @builtin(num_workgroups) nwg: vec3<u32>) {
115
+ {% if source.strided %}
116
+ // Strided mode has no `params` uniform (offsets come from compile-time
117
+ // constants), so it folds against the compile-time STRIDED_ROWS instead of
118
+ // params.rows. wg.y carries the high bits of the row past the maxComputeWorkgroupsPerDimension limit;
119
+ {% if source.stage == "max" %}
120
+ // the guard drops the over-dispatched tail before any rowMax[row] store.
121
+ {% else %}
122
+ // the guard drops the over-dispatched tail before any rowSum[row]/y[offset]
123
+ // store.
124
+ {% endif %}
125
+ let row = wg.x + wg.y * nwg.x;
126
+ if (row >= STRIDED_ROWS) { return; }
127
+ {% else %}
128
+ // 2D-folded row index: wg.y carries the high bits past the maxComputeWorkgroupsPerDimension
129
+ {% if source.stage == "max" %}
130
+ // workgroup-per-dimension dispatch limit (rows > maxComputeWorkgroupsPerDimension). The guard returns
131
+ // the whole over-dispatched workgroup before any read/write — without it the
132
+ // clamped OOB rowMax[row] store would corrupt the last real row. wg.x/wg.y/
133
+ {% else %}
134
+ // workgroup-per-dimension dispatch limit (rows > maxComputeWorkgroupsPerDimension). Return the whole
135
+ // over-dispatched workgroup before any read/write — without it the clamped
136
+ // OOB rowSum[row]/y[offset] stores would corrupt the last real row. wg.x/wg.y/
137
+ {% endif %}
138
+ // nwg.x are workgroup-uniform, so the return cannot diverge across a barrier.
139
+ let row = wg.x + wg.y * nwg.x;
140
+ if (row >= params.rows) { return; }
141
+ {% endif %}
142
+ {% if source.stage == "max" %}
143
+ var acc = -3.4028234663852886e38;
144
+ for (var k = lid.x; k < {{ count }}; k = k + WGS) {
145
+ acc = max(acc, f32(x[{{ offset_of }}]));
146
+ }
147
+ let rowM = reduce_max_f32(acc, lid.x);
148
+ if (lid.x == 0u) { rowMax[row] = rowM; }
149
+ {% else %}
150
+ let m = rowMax[row];
151
+ var acc = 0.0;
152
+ for (var k = lid.x; k < {{ count }}; k = k + WGS) {
153
+ let offset = {{ offset_of }};
154
+ // +Inf row max must yield NaN (ONNX semantics). `m - m` (Inf - Inf) is
155
+ // folded to 0 by fast-math backends, and a constant NaN bit pattern is
156
+ // rejected at WGSL const-evaluation — so derive the quiet NaN from the
157
+ // runtime value (any bits | 0x7fc00000 is a NaN).
158
+ let e = select(exp(f32(x[offset]) - m), bitcast<f32>(bitcast<u32>(m) | 0x7fc00000u), m > 3.4028234663852886e38);
159
+ y[offset] = {{ T }}(e);
160
+ acc = acc + e;
161
+ }
162
+ let rowSumValue = reduce_add_f32(acc, lid.x);
163
+ if (lid.x == 0u) { rowSum[row] = rowSumValue; }
164
+ {% endif %}
165
+ }
build/webgpu/softmax-strided-online-lane.wgsl.jinja ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Scratch-free online softmax/log-softmax for a non-last reduction axis. One
2
+ // invocation owns one (outer, inner) row, so neighboring lanes make coalesced
3
+ // reads at every reduction step. This is both the large-inner fast path and the
4
+ // capacity-safe fallback when row-stat scratch cannot be allocated.
5
+ {% set operation = source.op if source.op is defined else "softmax" %}
6
+ {% if T == "f16" %}
7
+ enable f16;
8
+ {% endif %}
9
+ {{ env.wgsl.resourceDeclarations }}
10
+ {% set stridedAxisUses = ["STRIDED_ROWS", "element_offset"] %}
11
+ // Compiled indexing for row-wise reductions over a non-last axis. A row is one
12
+ // slice along the reduction axis, indexed as outer * INNER_SIZE + inner.
13
+ {% set reduce_size = source.xShape[source.axis] %}
14
+ {% set inner_size = namespace(value=1) %}
15
+ {% for i in range(source.axis + 1, source.xShape | length) %}
16
+ {% set inner_size.value = inner_size.value * source.xShape[i] %}
17
+ {% endfor %}
18
+ {% set outer_size = namespace(value=1) %}
19
+ {% for i in range(source.axis) %}
20
+ {% set outer_size.value = outer_size.value * source.xShape[i] %}
21
+ {% endfor %}
22
+ {% set usesAll = stridedAxisUses is not defined %}
23
+ {% set usesElementOffset = usesAll or "element_offset" in stridedAxisUses %}
24
+ {% set usesRowForOffset = usesAll or "row_for_offset" in stridedAxisUses %}
25
+ {% set usesReduceSize = usesAll or usesElementOffset or "REDUCE_SIZE" in stridedAxisUses %}
26
+ {% set usesInnerSize = usesAll or usesElementOffset or "INNER_SIZE" in stridedAxisUses %}
27
+ {% set usesStridedRows = usesAll or "STRIDED_ROWS" in stridedAxisUses %}
28
+ {% set usesInnerSizeSafe = usesAll or usesElementOffset or usesRowForOffset or "INNER_SIZE_SAFE" in stridedAxisUses %}
29
+ {% set usesAxisStrideSafe = usesAll or usesRowForOffset or "AXIS_STRIDE_SAFE" in stridedAxisUses %}
30
+ {% if usesReduceSize %}
31
+ const REDUCE_SIZE: u32 = {{ reduce_size }}u;
32
+ {% endif %}
33
+ {% if usesInnerSize %}
34
+ const INNER_SIZE: u32 = {{ inner_size.value }}u;
35
+ {% endif %}
36
+ {% if usesStridedRows %}
37
+ // Total one-per-row workgroup count (product of every non-axis dim). Lets the
38
+ // row-wise reduce kernels 2D-fold their dispatch past the maxComputeWorkgroupsPerDimension limit and guard
39
+ // the over-dispatched tail without a runtime `params` uniform.
40
+ const STRIDED_ROWS: u32 = {{ outer_size.value * inner_size.value }}u;
41
+ {% endif %}
42
+ {% if usesInnerSizeSafe %}
43
+ const INNER_SIZE_SAFE: u32 = {{ inner_size.value if inner_size.value > 0 else 1 }}u;
44
+ {% endif %}
45
+ {% if usesAxisStrideSafe %}
46
+ const AXIS_STRIDE_SAFE: u32 = {{ (reduce_size * inner_size.value) if (reduce_size * inner_size.value) > 0 else 1 }}u;
47
+ {% endif %}
48
+
49
+ {% if usesElementOffset %}
50
+ // Flat offset of element k (along the reduce axis) of `row`.
51
+ fn element_offset(row: u32, k: u32) -> u32 {
52
+ let outer = row / INNER_SIZE_SAFE;
53
+ let inner = row % INNER_SIZE_SAFE;
54
+ return outer * REDUCE_SIZE * INNER_SIZE + k * INNER_SIZE + inner;
55
+ }
56
+
57
+ {% endif %}
58
+ {% if usesRowForOffset %}
59
+ // Row that owns the flat element `offset` (inverse of element_offset over
60
+ // the non-axis coordinates).
61
+ fn row_for_offset(offset: u32) -> u32 {
62
+ let outer = offset / AXIS_STRIDE_SAFE;
63
+ let inner = offset % INNER_SIZE_SAFE;
64
+ return outer * INNER_SIZE_SAFE + inner;
65
+ }
66
+ {%- endif -%}
67
+ // FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
68
+ // `m - m` finite so an empty lane / all--inf row contributes the exact
69
+ // accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
70
+ // a zero final denominator according to their public semantics. Using -inf
71
+ // here changes +inf-row behavior.
72
+ const FLT_MAX: f32 = 3.4028234663852886e38;
73
+
74
+ fn is_finite_f32(value: f32) -> bool {
75
+ return select(false, value <= FLT_MAX, value >= -FLT_MAX);
76
+ }
77
+
78
+ // x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
79
+ // exactly at the row max. `x - x` on an infinite max is a legal fast-math
80
+ // fold to 0, which would silently turn +inf rows finite — the explicit
81
+ // equality test keeps the NaN propagation of the serial kernels.
82
+ fn shifted_value(value: f32, maxValue: f32) -> f32 {
83
+ let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
84
+ return select(value - maxValue, 0.0, equalFiniteMax);
85
+ }
86
+
87
+ const WGS: u32 = {{ tunables.WORKGROUP_SIZE }}u;
88
+ @compute @workgroup_size(WGS)
89
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
90
+ @builtin(num_workgroups) nwg: vec3<u32>) {
91
+ let row = gid.x + gid.y * nwg.x * WGS;
92
+ if (row >= STRIDED_ROWS) { return; }
93
+
94
+ var row_max = -{{ "FLT_MAX" if operation == "logsoftmax" else "F32_MAX" }};
95
+ var row_sum = 0.0;
96
+ for (var k = 0u; k < REDUCE_SIZE; k += 1u) {
97
+ let value = f32(x[element_offset(row, k)]);
98
+ if (value > row_max) {
99
+ row_sum = row_sum * exp(row_max - value) + 1.0;
100
+ row_max = value;
101
+ } else {
102
+ row_sum += exp(value - row_max);
103
+ }
104
+ }
105
+
106
+ let sum_bits = bitcast<u32>(row_sum);
107
+ let sum_is_nan = (sum_bits & 0x7f800000u) == 0x7f800000u && (sum_bits & 0x007fffffu) != 0u;
108
+ for (var k = 0u; k < REDUCE_SIZE; k += 1u) {
109
+ let offset = element_offset(row, k);
110
+ var out = shifted_value(f32(x[offset]), row_max) - log(row_sum);
111
+ if (row_max > FLT_MAX || sum_is_nan) {
112
+ out = bitcast<f32>(bitcast<u32>(row_sum) | 0x7fc00000u);
113
+ }
114
+ y[offset] = {{ T }}(out);
115
+ }
116
+ }
build/webgpu/softmax-strided-packed4-tail.wgsl.jinja ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Online LogSoftmax for a non-last axis whose contiguous inner dimension has a
2
+ // one-to-three-element tail. Scalar bindings keep every outer slice legal even
3
+ // when its starting address is not vec4-aligned. Four adjacent inner rows share
4
+ // one workgroup and vec4 reduction tree, and uniform guards suppress invalid
5
+ // components of the final packed group.
6
+ {{ env.wgsl.resourceDeclarations }}
7
+
8
+ const WGS: u32 = {{ tunables.WORKGROUP_SIZE }}u;
9
+ {% set stridedAxisUses = ["REDUCE_SIZE", "INNER_SIZE", "STRIDED_ROWS", "INNER_SIZE_SAFE"] %}
10
+ // Compiled indexing for row-wise reductions over a non-last axis. A row is one
11
+ // slice along the reduction axis, indexed as outer * INNER_SIZE + inner.
12
+ {% set reduce_size = source.xShape[source.axis] %}
13
+ {% set inner_size = namespace(value=1) %}
14
+ {% for i in range(source.axis + 1, source.xShape | length) %}
15
+ {% set inner_size.value = inner_size.value * source.xShape[i] %}
16
+ {% endfor %}
17
+ {% set outer_size = namespace(value=1) %}
18
+ {% for i in range(source.axis) %}
19
+ {% set outer_size.value = outer_size.value * source.xShape[i] %}
20
+ {% endfor %}
21
+ {% set usesAll = stridedAxisUses is not defined %}
22
+ {% set usesElementOffset = usesAll or "element_offset" in stridedAxisUses %}
23
+ {% set usesRowForOffset = usesAll or "row_for_offset" in stridedAxisUses %}
24
+ {% set usesReduceSize = usesAll or usesElementOffset or "REDUCE_SIZE" in stridedAxisUses %}
25
+ {% set usesInnerSize = usesAll or usesElementOffset or "INNER_SIZE" in stridedAxisUses %}
26
+ {% set usesStridedRows = usesAll or "STRIDED_ROWS" in stridedAxisUses %}
27
+ {% set usesInnerSizeSafe = usesAll or usesElementOffset or usesRowForOffset or "INNER_SIZE_SAFE" in stridedAxisUses %}
28
+ {% set usesAxisStrideSafe = usesAll or usesRowForOffset or "AXIS_STRIDE_SAFE" in stridedAxisUses %}
29
+ {% if usesReduceSize %}
30
+ const REDUCE_SIZE: u32 = {{ reduce_size }}u;
31
+ {% endif %}
32
+ {% if usesInnerSize %}
33
+ const INNER_SIZE: u32 = {{ inner_size.value }}u;
34
+ {% endif %}
35
+ {% if usesStridedRows %}
36
+ // Total one-per-row workgroup count (product of every non-axis dim). Lets the
37
+ // row-wise reduce kernels 2D-fold their dispatch past the maxComputeWorkgroupsPerDimension limit and guard
38
+ // the over-dispatched tail without a runtime `params` uniform.
39
+ const STRIDED_ROWS: u32 = {{ outer_size.value * inner_size.value }}u;
40
+ {% endif %}
41
+ {% if usesInnerSizeSafe %}
42
+ const INNER_SIZE_SAFE: u32 = {{ inner_size.value if inner_size.value > 0 else 1 }}u;
43
+ {% endif %}
44
+ {% if usesAxisStrideSafe %}
45
+ const AXIS_STRIDE_SAFE: u32 = {{ (reduce_size * inner_size.value) if (reduce_size * inner_size.value) > 0 else 1 }}u;
46
+ {% endif %}
47
+
48
+ {% if usesElementOffset %}
49
+ // Flat offset of element k (along the reduce axis) of `row`.
50
+ fn element_offset(row: u32, k: u32) -> u32 {
51
+ let outer = row / INNER_SIZE_SAFE;
52
+ let inner = row % INNER_SIZE_SAFE;
53
+ return outer * REDUCE_SIZE * INNER_SIZE + k * INNER_SIZE + inner;
54
+ }
55
+
56
+ {% endif %}
57
+ {% if usesRowForOffset %}
58
+ // Row that owns the flat element `offset` (inverse of element_offset over
59
+ // the non-axis coordinates).
60
+ fn row_for_offset(offset: u32) -> u32 {
61
+ let outer = offset / AXIS_STRIDE_SAFE;
62
+ let inner = offset % INNER_SIZE_SAFE;
63
+ return outer * INNER_SIZE_SAFE + inner;
64
+ }
65
+ {%- endif -%}
66
+ // FLT_MAX, not -inf, as the online (m, d) accumulator init: merges must keep
67
+ // `m - m` finite so an empty lane / all--inf row contributes the exact
68
+ // accumulator identity (m, d) = (-FLT_MAX, 0). Operator epilogues interpret
69
+ // a zero final denominator according to their public semantics. Using -inf
70
+ // here changes +inf-row behavior.
71
+ const FLT_MAX: f32 = 3.4028234663852886e38;
72
+
73
+ fn is_finite_f32(value: f32) -> bool {
74
+ return select(false, value <= FLT_MAX, value >= -FLT_MAX);
75
+ }
76
+
77
+ // x - m that is exactly 0 when x equals a finite m, so exp(shifted) == 1
78
+ // exactly at the row max. `x - x` on an infinite max is a legal fast-math
79
+ // fold to 0, which would silently turn +inf rows finite — the explicit
80
+ // equality test keeps the NaN propagation of the serial kernels.
81
+ fn shifted_value(value: f32, maxValue: f32) -> f32 {
82
+ let equalFiniteMax = select(false, value == maxValue, is_finite_f32(maxValue));
83
+ return select(value - maxValue, 0.0, equalFiniteMax);
84
+ }
85
+ fn exp_shift(value: f32, maxValue: f32) -> f32 {
86
+ return exp(shifted_value(value, maxValue));
87
+ }
88
+ // Bit-exact NaN test: `v != v` can be folded to false by fast-math backends,
89
+ // and a constant NaN bit pattern is rejected at WGSL const-evaluation.
90
+ fn is_nan_f32(value: f32) -> bool {
91
+ let bits = bitcast<u32>(value);
92
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
93
+ }
94
+
95
+ const PACKED_INNER: u32 = (INNER_SIZE + 3u) / 4u;
96
+ const OUTER_SIZE: u32 = STRIDED_ROWS / INNER_SIZE_SAFE;
97
+ const PACKED_ROWS: u32 = OUTER_SIZE * PACKED_INNER;
98
+
99
+ var<workgroup> partial: array<vec4<f32>, WGS>;
100
+
101
+ fn reduce_max4(value: vec4<f32>, tid: u32) -> vec4<f32> {
102
+ partial[tid] = value;
103
+ workgroupBarrier();
104
+ for (var stride = WGS >> 1u; stride > 0u; stride = stride >> 1u) {
105
+ if (tid < stride) {
106
+ partial[tid] = max(partial[tid], partial[tid + stride]);
107
+ }
108
+ workgroupBarrier();
109
+ }
110
+ return partial[0];
111
+ }
112
+
113
+ fn reduce_add4(value: vec4<f32>, tid: u32) -> vec4<f32> {
114
+ partial[tid] = value;
115
+ workgroupBarrier();
116
+ for (var stride = WGS >> 1u; stride > 0u; stride = stride >> 1u) {
117
+ if (tid < stride) {
118
+ partial[tid] = partial[tid] + partial[tid + stride];
119
+ }
120
+ workgroupBarrier();
121
+ }
122
+ return partial[0];
123
+ }
124
+
125
+ fn stable_exp(value: f32, maximum: f32) -> f32 {
126
+ let nanv = bitcast<f32>(bitcast<u32>(maximum) | 0x7fc00000u);
127
+ return select(exp_shift(value, maximum), nanv, maximum > FLT_MAX);
128
+ }
129
+
130
+ fn normalized(value: f32, maximum: f32, sum: f32) -> f32 {
131
+ let rowIsNan = maximum > FLT_MAX || is_nan_f32(sum);
132
+ let nanOut = bitcast<f32>(bitcast<u32>(sum) | 0x7fc00000u);
133
+ return select(shifted_value(value, maximum) - log(sum), nanOut, rowIsNan);
134
+ }
135
+
136
+ @compute @workgroup_size(WGS)
137
+ fn main(
138
+ @builtin(workgroup_id) wg: vec3<u32>,
139
+ @builtin(local_invocation_id) lid: vec3<u32>,
140
+ @builtin(num_workgroups) nwg: vec3<u32>
141
+ ) {
142
+ let group = wg.x + wg.y * nwg.x;
143
+ if (group >= PACKED_ROWS) {
144
+ return;
145
+ }
146
+ let outer = group / PACKED_INNER;
147
+ let inner0 = (group % PACKED_INNER) * 4u;
148
+
149
+ var localMax = vec4<f32>(-FLT_MAX);
150
+ for (var k = lid.x; k < REDUCE_SIZE; k = k + WGS) {
151
+ let base = outer * REDUCE_SIZE * INNER_SIZE + k * INNER_SIZE + inner0;
152
+ if (inner0 < INNER_SIZE) { localMax.x = max(localMax.x, x[base]); }
153
+ if (inner0 + 1u < INNER_SIZE) { localMax.y = max(localMax.y, x[base + 1u]); }
154
+ if (inner0 + 2u < INNER_SIZE) { localMax.z = max(localMax.z, x[base + 2u]); }
155
+ if (inner0 + 3u < INNER_SIZE) { localMax.w = max(localMax.w, x[base + 3u]); }
156
+ }
157
+ let maximum = reduce_max4(localMax, lid.x);
158
+
159
+ var localSum = vec4<f32>(0.0);
160
+ for (var k = lid.x; k < REDUCE_SIZE; k = k + WGS) {
161
+ let base = outer * REDUCE_SIZE * INNER_SIZE + k * INNER_SIZE + inner0;
162
+ if (inner0 < INNER_SIZE) { localSum.x = localSum.x + stable_exp(x[base], maximum.x); }
163
+ if (inner0 + 1u < INNER_SIZE) { localSum.y = localSum.y + stable_exp(x[base + 1u], maximum.y); }
164
+ if (inner0 + 2u < INNER_SIZE) { localSum.z = localSum.z + stable_exp(x[base + 2u], maximum.z); }
165
+ if (inner0 + 3u < INNER_SIZE) { localSum.w = localSum.w + stable_exp(x[base + 3u], maximum.w); }
166
+ }
167
+ let sum = reduce_add4(localSum, lid.x);
168
+
169
+ for (var k = lid.x; k < REDUCE_SIZE; k = k + WGS) {
170
+ let base = outer * REDUCE_SIZE * INNER_SIZE + k * INNER_SIZE + inner0;
171
+ if (inner0 < INNER_SIZE) { y[base] = normalized(x[base], maximum.x, sum.x); }
172
+ if (inner0 + 1u < INNER_SIZE) { y[base + 1u] = normalized(x[base + 1u], maximum.y, sum.y); }
173
+ if (inner0 + 2u < INNER_SIZE) { y[base + 2u] = normalized(x[base + 2u], maximum.z, sum.z); }
174
+ if (inner0 + 3u < INNER_SIZE) { y[base + 3u] = normalized(x[base + 3u], maximum.w, sum.w); }
175
+ }
176
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,720 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.LogSoftmax",
3
+ "fixtureArrays": {
4
+ "ort_seed_123_input_x": [1.0856307, 0.99734545, 0.2829785, 1.5062947, 0.5786002, 1.6514366, 2.4266791, 0.42891264, 1.2659363, 0.8667404, 0.6788862, 0.09470897, 1.4913896, 0.638902, 0.44398195, 0.43435127, 2.20593, 2.1867862, 1.004054, 0.3861864, 0.7373686, 1.4907321, 0.9358339, 1.175829, 1.2538806, 0.6377515, 0.9071052, 1.4286807, 0.14006872, 0.8617549, 0.25561938, 2.798589, 1.7715331, 0.69987726, 0.92746246, 0.17363568, 0.002845916, 0.6882227, 0.87953633, 0.28362733, 0.8053665, 1.7276695, 0.3908998, 0.57380587, 0.33858904, 0.011830495, 2.3923652, 0.41291216, 0.978736, 2.2381434, 1.2940853, 1.0387882, 1.7437122, 0.79806274, 0.02968323, 1.0693159, 0.8907064, 1.7548862, 1.4956441, 1.0693927],
5
+ "onnx_backend_logsoftmax_input_x": [1.764052391052246, 0.40015721321105957, 0.978738009929657, 2.2408931255340576, 1.8675580024719238, 0.9772778749465942, 0.9500884413719177, 0.15135720372200012, 0.10321885347366333, 0.4105985164642334, 0.14404356479644775, 1.4542734622955322, 0.7610377073287964, 0.12167501449584961, 0.44386324286460876, 0.3336743414402008, 1.4940791130065918, 0.2051582634449005, 0.3130677044391632, 0.8540957570075989, 2.5529897212982178, 0.653618574142456, 0.8644362092018127, 0.7421650290489197, 2.269754648208618, 1.4543657302856445, 0.04575851559638977, 0.18718385696411133, 1.5327792167663574, 1.4693588018417358, 0.154947429895401, 0.37816253304481506, 0.8877857327461243, 1.980796456336975, 0.34791216254234314, 0.15634897351264954, 1.2302906513214111, 1.202379822731018, 0.38732680678367615, 0.302302747964859, 1.0485529899597168, 1.420017957687378, 1.7062702178955078, 1.950775384902954, 0.5096521973609924, 0.4380742907524109, 1.2527953386306763, 0.7774903774261475, 1.6138978004455566, 0.21274028718471527, 0.8954665660858154, 0.38690251111984253, 0.5108051300048828, 1.18063223361969, 0.02818222902715206, 0.4283318817615509, 0.06651721894741058, 0.30247190594673157, 0.6343221068382263, 0.3627411723136902]
6
+ },
7
+ "cases": [
8
+ {
9
+ "name": "strided_packed4_tail_rank3_axis1_inner6",
10
+ "provenance": {
11
+ "notes": "Exercises the scalar-aligned packed-4 strided reducer with two valid lanes in the final inner group."
12
+ },
13
+ "attrs": { "axis": 1 },
14
+ "inputs": {
15
+ "x": {
16
+ "dtype": "float32",
17
+ "shape": [2, 17, 6],
18
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.07, "scale": 2.0 }
19
+ }
20
+ },
21
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 17, 6], "tolerance": 0.00001, "relTolerance": 0.00001 } }
22
+ },
23
+ {
24
+ "name": "strided_capacity_large_axis_tail_boundary",
25
+ "provenance": {
26
+ "notes": "Compact correctness sibling for the large-inner selector crossover. The case-local threshold selects the capacity path while axis length 2048 and inner=6 retain the production gate and non-multiple-of-four tail."
27
+ },
28
+ "tunables": { "STRIDED_CAPACITY_MIN_ROWS": 1 },
29
+ "attrs": { "axis": 1 },
30
+ "inputs": {
31
+ "x": {
32
+ "dtype": "float32",
33
+ "shape": [1, 2048, 6],
34
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.007, "scale": 2.0 }
35
+ }
36
+ },
37
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2048, 6], "tolerance": 0.00001, "relTolerance": 0.00001 } }
38
+ },
39
+ {
40
+ "name": "dispatch_cliff_online_2dfold_65540x40",
41
+ "attrs": { "axis": -1 },
42
+ "inputs": {
43
+ "x": {
44
+ "dtype": "float32",
45
+ "shape": [65540, 40],
46
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.007, "scale": 2.0 }
47
+ }
48
+ },
49
+ "outputs": { "y": { "dtype": "float32", "shape": [65540, 40], "tolerance": 0.0001, "relTolerance": 0.0001 } }
50
+ },
51
+ {
52
+ "name": "dispatch_cliff_stable3pass_513x32768",
53
+ "attrs": { "axis": -1 },
54
+ "inputs": {
55
+ "x": {
56
+ "dtype": "float32",
57
+ "shape": [513, 32768],
58
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.031, "scale": 3.0 }
59
+ }
60
+ },
61
+ "outputs": { "y": { "dtype": "float32", "shape": [513, 32768], "tolerance": 0.0001, "relTolerance": 0.00001 } }
62
+ },
63
+ {
64
+ "name": "rank2_last_axis",
65
+ "inputs": {
66
+ "x": {
67
+ "dtype": "float32",
68
+ "shape": [2, 4],
69
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, -2.0, 0.0, 2.0, 4.0] }
70
+ }
71
+ },
72
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 4] } }
73
+ },
74
+ {
75
+ "name": "packed_rows_tail_axis1_33x32",
76
+ "provenance": {
77
+ "notes": "One row beyond a full 32-row packed workgroup locks the shared portable multirow kernel's partial-workgroup barrier and output guards."
78
+ },
79
+ "attrs": { "axis": 1 },
80
+ "inputs": {
81
+ "x": {
82
+ "dtype": "float32",
83
+ "shape": [33, 32],
84
+ "data": { "kind": "fillFloat32", "sinStep": 0.037, "cosStep": 0.019, "scale": 2.0 }
85
+ }
86
+ },
87
+ "outputs": { "y": { "dtype": "float32", "shape": [33, 32], "tolerance": 0.00001, "relTolerance": 0.00001 } }
88
+ },
89
+ {
90
+ "name": "many_short_rows_axis1_64x32",
91
+ "provenance": {
92
+ "notes": "Compact correctness sibling for many-short-row LogSoftmax benchmarking; keeps the reduced axis short without making the correctness suite benchmark-sized."
93
+ },
94
+ "attrs": { "axis": 1 },
95
+ "inputs": {
96
+ "x": {
97
+ "dtype": "float32",
98
+ "shape": [64, 32],
99
+ "data": { "kind": "fillFloat32", "sinStep": 0.037, "cosStep": 0.019, "scale": 2.0 }
100
+ }
101
+ },
102
+ "outputs": { "y": { "dtype": "float32", "shape": [64, 32], "tolerance": 0.00001, "relTolerance": 0.00001 } }
103
+ },
104
+ {
105
+ "name": "ort_simple_axis1",
106
+ "provenance": {
107
+ "source": "onnxruntime/test/providers/cpu/math/logsoftmax_test.cc",
108
+ "test": "LogSoftmaxOperator.Simple"
109
+ },
110
+ "attrs": { "axis": 1 },
111
+ "inputs": {
112
+ "x": { "dtype": "float32", "shape": [1, 3], "data": { "kind": "values", "values": [-1.0, 0.0, 1.0] } }
113
+ },
114
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 3], "tolerance": 0.000001 } }
115
+ },
116
+ {
117
+ "name": "ort_large_number_axis1",
118
+ "provenance": {
119
+ "source": "onnxruntime/test/providers/cpu/math/logsoftmax_test.cc",
120
+ "test": "LogSoftmaxOperator.LargeNumber"
121
+ },
122
+ "attrs": { "axis": 1 },
123
+ "inputs": {
124
+ "x": {
125
+ "dtype": "float32",
126
+ "shape": [2, 4],
127
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 10000.0, 10001.0, 10002.0, 10003.0] }
128
+ }
129
+ },
130
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 4], "tolerance": 0.0005 } }
131
+ },
132
+ {
133
+ "name": "rank3_last_axis_f16",
134
+ "attrs": { "axis": -1 },
135
+ "inputs": { "x": { "dtype": "float16", "shape": [2, 2, 3] } },
136
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 2, 3] } },
137
+ "tolerance": 0.002
138
+ },
139
+ {
140
+ "name": "float32_min_uniform_regression",
141
+ "attrs": { "axis": -1 },
142
+ "inputs": {
143
+ "x": { "dtype": "float32", "shape": [1, 64], "data": { "kind": "constant", "value": -3.4028234663852886e+38 } }
144
+ },
145
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 64], "tolerance": 0.000001 } }
146
+ },
147
+ {
148
+ "name": "longrow_split_axis1_1x65536",
149
+ "attrs": { "axis": 1 },
150
+ "inputs": {
151
+ "x": {
152
+ "dtype": "float32",
153
+ "shape": [1, 65536],
154
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.031, "scale": 3.0 }
155
+ }
156
+ },
157
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 65536], "tolerance": 0.0001, "relTolerance": 0.00001 } }
158
+ },
159
+ {
160
+ "name": "longrow_split_float32_min_uniform",
161
+ "attrs": { "axis": -1 },
162
+ "inputs": {
163
+ "x": {
164
+ "dtype": "float32",
165
+ "shape": [1, 65536],
166
+ "data": { "kind": "constant", "value": -3.4028234663852886e+38 }
167
+ }
168
+ },
169
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 65536], "tolerance": 0.00001, "relTolerance": 0.00001 } }
170
+ },
171
+ {
172
+ "name": "positive_infinity_rows_return_nan",
173
+ "attrs": { "axis": -1 },
174
+ "inputs": {
175
+ "x": {
176
+ "dtype": "float32",
177
+ "shape": [2, 4],
178
+ "data": { "kind": "values", "values": ["Infinity", 1.0, 2.0, -3.0, "Infinity", "Infinity", 5.0, 5.0] }
179
+ }
180
+ },
181
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 4], "tolerance": 0.000001, "allowNaN": true } }
182
+ },
183
+ {
184
+ "name": "singleton_axis_mixed_finite_nonfinite_f16",
185
+ "attrs": { "axis": 0 },
186
+ "inputs": {
187
+ "x": {
188
+ "dtype": "float16",
189
+ "shape": [1, 4],
190
+ "data": { "kind": "values", "values": [0.25, "Infinity", "-Infinity", "NaN"] }
191
+ }
192
+ },
193
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 4], "tolerance": 0, "allowNaN": true } }
194
+ },
195
+ {
196
+ "name": "empty_last_axis_dim_zero",
197
+ "provenance": {
198
+ "source": "onnxruntime/test/providers/cpu/math/softmax_test.cc",
199
+ "test": "SoftmaxOperator.DimWithZero",
200
+ "notes": "LogSoftmax analogue of ORT's zero-dimension Softmax coverage; the output is empty, so the framework plans the op as a no-op (zero dispatch) per ONNX zero-dimension semantics."
201
+ },
202
+ "attrs": { "axis": -1 },
203
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 0], "data": { "kind": "values", "values": [] } } },
204
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 0], "tolerance": 0 } }
205
+ },
206
+ {
207
+ "name": "empty_axis0_dim_zero",
208
+ "provenance": {
209
+ "source": "onnxruntime/test/providers/cpu/math/softmax_test.cc",
210
+ "test": "SoftmaxOperator.DimWithZero",
211
+ "notes": "LogSoftmax leading-axis analogue of ORT's zero-dimension Softmax coverage; the reduced axis itself has size zero and the output is empty."
212
+ },
213
+ "attrs": { "axis": 0 },
214
+ "inputs": { "x": { "dtype": "float32", "shape": [0, 1], "data": { "kind": "values", "values": [] } } },
215
+ "outputs": { "y": { "dtype": "float32", "shape": [0, 1], "tolerance": 0 } }
216
+ },
217
+ {
218
+ "name": "axis0_non_last_rank2",
219
+ "attrs": { "axis": 0 },
220
+ "inputs": {
221
+ "x": {
222
+ "dtype": "float32",
223
+ "shape": [3, 4],
224
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 2.0, 1.0, 4.0, 3.0, 3.0, 4.0, 1.0, 2.0] }
225
+ }
226
+ },
227
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4], "tolerance": 0.000001 } }
228
+ },
229
+ {
230
+ "name": "ort_opset13_rank3_axis0_large_offsets",
231
+ "provenance": {
232
+ "source": "onnxruntime/test/providers/cpu/math/logsoftmax_test.cc",
233
+ "test": "LogSoftmaxOperator.ThreeDimsAxis0",
234
+ "notes": "Opset-13 axis=0 semantics reduce only the leading axis; large per-column offsets exercise stable strided-axis normalization."
235
+ },
236
+ "attrs": { "axis": 0 },
237
+ "inputs": {
238
+ "x": {
239
+ "dtype": "float32",
240
+ "shape": [3, 2, 2],
241
+ "data": {
242
+ "kind": "values",
243
+ "values": [10000.0, -10000.0, 0.0, 5.0, 10001.0, -9999.0, -1.0, 6.0, 9999.0, -10001.0, 2.0, 4.0]
244
+ }
245
+ }
246
+ },
247
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 2, 2], "tolerance": 0.000001 } }
248
+ },
249
+ {
250
+ "name": "axis0_positive_infinity_rows_return_nan",
251
+ "provenance": {
252
+ "source": "onnxruntime/test/providers/cpu/math/logsoftmax_test.cc",
253
+ "test": "LogSoftmaxOperator.ThreeDimsAxis0",
254
+ "notes": "Positive infinity must poison the whole reduction group even when the reduced axis is strided instead of contiguous."
255
+ },
256
+ "attrs": { "axis": 0 },
257
+ "inputs": {
258
+ "x": {
259
+ "dtype": "float32",
260
+ "shape": [2, 2, 2],
261
+ "data": { "kind": "values", "values": ["Infinity", 1.0, 2.0, "Infinity", 3.0, "Infinity", "Infinity", 4.0] }
262
+ }
263
+ },
264
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 2, 2], "tolerance": 0, "allowNaN": true } }
265
+ },
266
+ {
267
+ "name": "rank6_last_axis",
268
+ "attrs": { "axis": -1 },
269
+ "inputs": {
270
+ "x": {
271
+ "dtype": "float32",
272
+ "shape": [1, 2, 1, 2, 1, 3],
273
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, -1.0, 0.0, 1.0, 4.0, 4.0, 5.0, -2.0, -3.0, -4.0] }
274
+ }
275
+ },
276
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 1, 2, 1, 3], "tolerance": 0.000001 } }
277
+ },
278
+ {
279
+ "name": "rank3_axis1_middle_large_values",
280
+ "attrs": { "axis": 1 },
281
+ "inputs": {
282
+ "x": {
283
+ "dtype": "float32",
284
+ "shape": [2, 3, 2],
285
+ "data": {
286
+ "kind": "values",
287
+ "values": [1000.0, -1000.0, 1001.0, -1001.0, 999.0, -999.0, -50.0, 50.0, -51.0, 49.0, -52.0, 48.0]
288
+ }
289
+ }
290
+ },
291
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 2], "tolerance": 0.000001 } }
292
+ },
293
+ {
294
+ "name": "ort_opset13_rank3_axis1_seed123",
295
+ "provenance": {
296
+ "source": "onnxruntime/test/providers/cpu/math/logsoftmax_test.cc",
297
+ "test": "LogSoftmaxOperator.ThreeDimsAxis1_opset13"
298
+ },
299
+ "attrs": { "axis": 1 },
300
+ "inputs": {
301
+ "x": {
302
+ "dtype": "float32",
303
+ "shape": [3, 4, 5],
304
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_seed_123_input_x" } }
305
+ }
306
+ },
307
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0.000001 } }
308
+ },
309
+ {
310
+ "name": "ort_opset13_rank3_axis2_seed123",
311
+ "provenance": {
312
+ "source": "onnxruntime/test/providers/cpu/math/logsoftmax_test.cc",
313
+ "test": "LogSoftmaxOperator.ThreeDimsAxis2_opset13"
314
+ },
315
+ "attrs": { "axis": 2 },
316
+ "inputs": {
317
+ "x": {
318
+ "dtype": "float32",
319
+ "shape": [3, 4, 5],
320
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_seed_123_input_x" } }
321
+ }
322
+ },
323
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0.000001 } }
324
+ },
325
+ {
326
+ "name": "rank3_axis_minus2_middle_strided",
327
+ "attrs": { "axis": -2 },
328
+ "inputs": {
329
+ "x": {
330
+ "dtype": "float32",
331
+ "shape": [1, 3, 3],
332
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 2.0, 3.0, 4.0, 3.0, 4.0, 5.0] }
333
+ }
334
+ },
335
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 3, 3], "tolerance": 0.000001 } }
336
+ },
337
+ {
338
+ "name": "ort_opset13_rank3_default_axis_seed123",
339
+ "provenance": {
340
+ "source": "onnxruntime/test/providers/cpu/math/logsoftmax_test.cc",
341
+ "test": "LogSoftmaxOperator.ThreeDimsDefaultAxis_opset13"
342
+ },
343
+ "inputs": {
344
+ "x": {
345
+ "dtype": "float32",
346
+ "shape": [3, 4, 5],
347
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_seed_123_input_x" } }
348
+ }
349
+ },
350
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0.000001 } }
351
+ },
352
+ {
353
+ "name": "ort_negative_axis_rank3_last_seed123",
354
+ "provenance": {
355
+ "source": "onnxruntime/test/providers/cpu/math/logsoftmax_test.cc",
356
+ "test": "LogSoftmaxOperator.ThreeDimsNegativeAxis"
357
+ },
358
+ "attrs": { "axis": -1 },
359
+ "inputs": {
360
+ "x": {
361
+ "dtype": "float32",
362
+ "shape": [3, 4, 5],
363
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_seed_123_input_x" } }
364
+ }
365
+ },
366
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0.000001 } }
367
+ },
368
+ {
369
+ "name": "ort_axis1_large_dim_1025",
370
+ "provenance": {
371
+ "source": "onnxruntime/test/providers/cpu/math/logsoftmax_test.cc",
372
+ "test": "LogSoftmaxOperator.2DInputReduceOnAxis1WithLargeDim"
373
+ },
374
+ "attrs": { "axis": 1 },
375
+ "inputs": {
376
+ "x": {
377
+ "dtype": "float32",
378
+ "shape": [1, 1025],
379
+ "data": {
380
+ "kind": "values",
381
+ "values": [0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.2, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.3, 0.31, 0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38, 0.39, 0.4, 0.41, 0.42, 0.43, 0.44, 0.45, 0.46, 0.47, 0.48, 0.49, 0.5, 0.51, 0.52, 0.53, 0.54, 0.55, 0.56, 0.57, 0.58, 0.59, 0.6, 0.61, 0.62, 0.63, 0.64, 0.65, 0.66, 0.67, 0.68, 0.69, 0.7, 0.71, 0.72, 0.73, 0.74, 0.75, 0.76, 0.77, 0.78, 0.79, 0.8, 0.81, 0.82, 0.83, 0.84, 0.85, 0.86, 0.87, 0.88, 0.89, 0.9, 0.91, 0.92, 0.93, 0.94, 0.95, 0.96, 0.97, 0.98, 0.99, 1.0, 1.01, 1.02, 1.03, 1.04, 1.05, 1.06, 1.07, 1.08, 1.09, 1.1, 1.11, 1.12, 1.13, 1.14, 1.15, 1.16, 1.17, 1.18, 1.19, 1.2, 1.21, 1.22, 1.23, 1.24, 1.25, 1.26, 1.27, 1.28, 1.29, 1.3, 1.31, 1.32, 1.33, 1.34, 1.35, 1.36, 1.37, 1.38, 1.39, 1.4, 1.41, 1.42, 1.43, 1.44, 1.45, 1.46, 1.47, 1.48, 1.49, 1.5, 1.51, 1.52, 1.53, 1.54, 1.55, 1.56, 1.57, 1.58, 1.59, 1.6, 1.61, 1.62, 1.63, 1.64, 1.65, 1.66, 1.67, 1.68, 1.69, 1.7, 1.71, 1.72, 1.73, 1.74, 1.75, 1.76, 1.77, 1.78, 1.79, 1.8, 1.81, 1.82, 1.83, 1.84, 1.85, 1.86, 1.87, 1.88, 1.89, 1.9, 1.91, 1.92, 1.93, 1.94, 1.95, 1.96, 1.97, 1.98, 1.99, 2.0, 2.01, 2.02, 2.03, 2.04, 2.05, 2.06, 2.07, 2.08, 2.09, 2.1, 2.11, 2.12, 2.13, 2.14, 2.15, 2.16, 2.17, 2.18, 2.19, 2.2, 2.21, 2.22, 2.23, 2.24, 2.25, 2.26, 2.27, 2.28, 2.29, 2.3, 2.31, 2.32, 2.33, 2.34, 2.35, 2.36, 2.37, 2.38, 2.39, 2.4, 2.41, 2.42, 2.43, 2.44, 2.45, 2.46, 2.47, 2.48, 2.49, 2.5, 2.51, 2.52, 2.53, 2.54, 2.55, 2.56, 2.57, 2.58, 2.59, 2.6, 2.61, 2.62, 2.63, 2.64, 2.65, 2.66, 2.67, 2.68, 2.69, 2.7, 2.71, 2.72, 2.73, 2.74, 2.75, 2.76, 2.77, 2.78, 2.79, 2.8, 2.81, 2.82, 2.83, 2.84, 2.85, 2.86, 2.87, 2.88, 2.89, 2.9, 2.91, 2.92, 2.93, 2.94, 2.95, 2.96, 2.97, 2.98, 2.99, 3.0, 3.01, 3.02, 3.03, 3.04, 3.05, 3.06, 3.07, 3.08, 3.09, 3.1, 3.11, 3.12, 3.13, 3.14, 3.15, 3.16, 3.17, 3.18, 3.19, 3.2, 3.21, 3.22, 3.23, 3.24, 3.25, 3.26, 3.27, 3.28, 3.29, 3.3, 3.31, 3.32, 3.33, 3.34, 3.35, 3.36, 3.37, 3.38, 3.39, 3.4, 3.41, 3.42, 3.43, 3.44, 3.45, 3.46, 3.47, 3.48, 3.49, 3.5, 3.51, 3.52, 3.53, 3.54, 3.55, 3.56, 3.57, 3.58, 3.59, 3.6, 3.61, 3.62, 3.63, 3.64, 3.65, 3.66, 3.67, 3.68, 3.69, 3.7, 3.71, 3.72, 3.73, 3.74, 3.75, 3.76, 3.77, 3.78, 3.79, 3.8, 3.81, 3.82, 3.83, 3.84, 3.85, 3.86, 3.87, 3.88, 3.89, 3.9, 3.91, 3.92, 3.93, 3.94, 3.95, 3.96, 3.97, 3.98, 3.99, 4.0, 4.01, 4.02, 4.03, 4.04, 4.05, 4.06, 4.07, 4.08, 4.09, 4.1, 4.11, 4.12, 4.13, 4.14, 4.15, 4.16, 4.17, 4.18, 4.19, 4.2, 4.21, 4.22, 4.23, 4.24, 4.25, 4.26, 4.27, 4.28, 4.29, 4.3, 4.31, 4.32, 4.33, 4.34, 4.35, 4.36, 4.37, 4.38, 4.39, 4.4, 4.41, 4.42, 4.43, 4.44, 4.45, 4.46, 4.47, 4.48, 4.49, 4.5, 4.51, 4.52, 4.53, 4.54, 4.55, 4.56, 4.57, 4.58, 4.59, 4.6, 4.61, 4.62, 4.63, 4.64, 4.65, 4.66, 4.67, 4.68, 4.69, 4.7, 4.71, 4.72, 4.73, 4.74, 4.75, 4.76, 4.77, 4.78, 4.79, 4.8, 4.81, 4.82, 4.83, 4.84, 4.85, 4.86, 4.87, 4.88, 4.89, 4.9, 4.91, 4.92, 4.93, 4.94, 4.95, 4.96, 4.97, 4.98, 4.99, 5.0, 5.01, 5.02, 5.03, 5.04, 5.05, 5.06, 5.07, 5.08, 5.09, 5.1, 5.11, 5.12, 5.13, 5.14, 5.15, 5.16, 5.17, 5.18, 5.19, 5.2, 5.21, 5.22, 5.23, 5.24, 5.25, 5.26, 5.27, 5.28, 5.29, 5.3, 5.31, 5.32, 5.33, 5.34, 5.35, 5.36, 5.37, 5.38, 5.39, 5.4, 5.41, 5.42, 5.43, 5.44, 5.45, 5.46, 5.47, 5.48, 5.49, 5.5, 5.51, 5.52, 5.53, 5.54, 5.55, 5.56, 5.57, 5.58, 5.59, 5.6, 5.61, 5.62, 5.63, 5.64, 5.65, 5.66, 5.67, 5.68, 5.69, 5.7, 5.71, 5.72, 5.73, 5.74, 5.75, 5.76, 5.77, 5.78, 5.79, 5.8, 5.81, 5.82, 5.83, 5.84, 5.85, 5.86, 5.87, 5.88, 5.89, 5.9, 5.91, 5.92, 5.93, 5.94, 5.95, 5.96, 5.97, 5.98, 5.99, 6.0, 6.01, 6.02, 6.03, 6.04, 6.05, 6.06, 6.07, 6.08, 6.09, 6.1, 6.11, 6.12, 6.13, 6.14, 6.15, 6.16, 6.17, 6.18, 6.19, 6.2, 6.21, 6.22, 6.23, 6.24, 6.25, 6.26, 6.27, 6.28, 6.29, 6.3, 6.31, 6.32, 6.33, 6.34, 6.35, 6.36, 6.37, 6.38, 6.39, 6.4, 6.41, 6.42, 6.43, 6.44, 6.45, 6.46, 6.47, 6.48, 6.49, 6.5, 6.51, 6.52, 6.53, 6.54, 6.55, 6.56, 6.57, 6.58, 6.59, 6.6, 6.61, 6.62, 6.63, 6.64, 6.65, 6.66, 6.67, 6.68, 6.69, 6.7, 6.71, 6.72, 6.73, 6.74, 6.75, 6.76, 6.77, 6.78, 6.79, 6.8, 6.81, 6.82, 6.83, 6.84, 6.85, 6.86, 6.87, 6.88, 6.89, 6.9, 6.91, 6.92, 6.93, 6.94, 6.95, 6.96, 6.97, 6.98, 6.99, 7.0, 7.01, 7.02, 7.03, 7.04, 7.05, 7.06, 7.07, 7.08, 7.09, 7.1, 7.11, 7.12, 7.13, 7.14, 7.15, 7.16, 7.17, 7.18, 7.19, 7.2, 7.21, 7.22, 7.23, 7.24, 7.25, 7.26, 7.27, 7.28, 7.29, 7.3, 7.31, 7.32, 7.33, 7.34, 7.35, 7.36, 7.37, 7.38, 7.39, 7.4, 7.41, 7.42, 7.43, 7.44, 7.45, 7.46, 7.47, 7.48, 7.49, 7.5, 7.51, 7.52, 7.53, 7.54, 7.55, 7.56, 7.57, 7.58, 7.59, 7.6, 7.61, 7.62, 7.63, 7.64, 7.65, 7.66, 7.67, 7.68, 7.69, 7.7, 7.71, 7.72, 7.73, 7.74, 7.75, 7.76, 7.77, 7.78, 7.79, 7.8, 7.81, 7.82, 7.83, 7.84, 7.85, 7.86, 7.87, 7.88, 7.89, 7.9, 7.91, 7.92, 7.93, 7.94, 7.95, 7.96, 7.97, 7.98, 7.99, 8.0, 8.01, 8.02, 8.03, 8.04, 8.05, 8.06, 8.07, 8.08, 8.09, 8.1, 8.11, 8.12, 8.13, 8.14, 8.15, 8.16, 8.17, 8.18, 8.19, 8.2, 8.21, 8.22, 8.23, 8.24, 8.25, 8.26, 8.27, 8.28, 8.29, 8.3, 8.31, 8.32, 8.33, 8.34, 8.35, 8.36, 8.37, 8.38, 8.39, 8.4, 8.41, 8.42, 8.43, 8.44, 8.45, 8.46, 8.47, 8.48, 8.49, 8.5, 8.51, 8.52, 8.53, 8.54, 8.55, 8.56, 8.57, 8.58, 8.59, 8.6, 8.61, 8.62, 8.63, 8.64, 8.65, 8.66, 8.67, 8.68, 8.69, 8.7, 8.71, 8.72, 8.73, 8.74, 8.75, 8.76, 8.77, 8.78, 8.79, 8.8, 8.81, 8.82, 8.83, 8.84, 8.85, 8.86, 8.87, 8.88, 8.89, 8.9, 8.91, 8.92, 8.93, 8.94, 8.95, 8.96, 8.97, 8.98, 8.99, 9.0, 9.01, 9.02, 9.03, 9.04, 9.05, 9.06, 9.07, 9.08, 9.09, 9.1, 9.11, 9.12, 9.13, 9.14, 9.15, 9.16, 9.17, 9.18, 9.19, 9.2, 9.21, 9.22, 9.23, 9.24, 9.25, 9.26, 9.27, 9.28, 9.29, 9.3, 9.31, 9.32, 9.33, 9.34, 9.35, 9.36, 9.37, 9.38, 9.39, 9.4, 9.41, 9.42, 9.43, 9.44, 9.45, 9.46, 9.47, 9.48, 9.49, 9.5, 9.51, 9.52, 9.53, 9.54, 9.55, 9.56, 9.57, 9.58, 9.59, 9.6, 9.61, 9.62, 9.63, 9.64, 9.65, 9.66, 9.67, 9.68, 9.69, 9.7, 9.71, 9.72, 9.73, 9.74, 9.75, 9.76, 9.77, 9.78, 9.79, 9.8, 9.81, 9.82, 9.83, 9.84, 9.85, 9.86, 9.87, 9.88, 9.89, 9.9, 9.91, 9.92, 9.93, 9.94, 9.95, 9.96, 9.97, 9.98, 9.99, 10.0, 10.01, 10.02, 10.03, 10.04, 10.05, 10.06, 10.07, 10.08, 10.09, 10.1, 10.11, 10.12, 10.13, 10.14, 10.15, 10.16, 10.17, 10.18, 10.19, 10.2, 10.21, 10.22, 10.23, 10.24, 10.25]
382
+ }
383
+ }
384
+ },
385
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1025], "tolerance": 0.00001 } }
386
+ },
387
+ {
388
+ "name": "onnx_backend_logsoftmax_axis_0",
389
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_logsoftmax_axis_0" },
390
+ "attrs": { "axis": 0 },
391
+ "inputs": {
392
+ "x": {
393
+ "dtype": "float32",
394
+ "shape": [3, 4, 5],
395
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_logsoftmax_input_x" } }
396
+ }
397
+ },
398
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0.00001 } }
399
+ },
400
+ {
401
+ "name": "onnx_backend_logsoftmax_axis_1",
402
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_logsoftmax_axis_1" },
403
+ "attrs": { "axis": 1 },
404
+ "inputs": {
405
+ "x": {
406
+ "dtype": "float32",
407
+ "shape": [3, 4, 5],
408
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_logsoftmax_input_x" } }
409
+ }
410
+ },
411
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0.00001 } }
412
+ },
413
+ {
414
+ "name": "onnx_backend_logsoftmax_axis_2",
415
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_logsoftmax_axis_2" },
416
+ "attrs": { "axis": 2 },
417
+ "inputs": {
418
+ "x": {
419
+ "dtype": "float32",
420
+ "shape": [3, 4, 5],
421
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_logsoftmax_input_x" } }
422
+ }
423
+ },
424
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0.00001 } }
425
+ },
426
+ {
427
+ "name": "onnx_backend_logsoftmax_default_axis",
428
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_logsoftmax_default_axis" },
429
+ "inputs": {
430
+ "x": {
431
+ "dtype": "float32",
432
+ "shape": [3, 4, 5],
433
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_logsoftmax_input_x" } }
434
+ }
435
+ },
436
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0.00001 } }
437
+ },
438
+ {
439
+ "name": "onnx_backend_logsoftmax_example_1",
440
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_logsoftmax_example_1" },
441
+ "inputs": {
442
+ "x": { "dtype": "float32", "shape": [1, 3], "data": { "kind": "values", "values": [-1.0, 0.0, 1.0] } }
443
+ },
444
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 3], "tolerance": 0.00001 } }
445
+ },
446
+ {
447
+ "name": "onnx_backend_logsoftmax_large_number",
448
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_logsoftmax_large_number" },
449
+ "inputs": {
450
+ "x": {
451
+ "dtype": "float32",
452
+ "shape": [2, 4],
453
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 10000.0, 10001.0, 10002.0, 10003.0] }
454
+ }
455
+ },
456
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 4], "tolerance": 0.00001 } }
457
+ },
458
+ {
459
+ "name": "onnx_backend_logsoftmax_negative_axis",
460
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_logsoftmax_negative_axis" },
461
+ "attrs": { "axis": -1 },
462
+ "inputs": {
463
+ "x": {
464
+ "dtype": "float32",
465
+ "shape": [3, 4, 5],
466
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_logsoftmax_input_x" } }
467
+ }
468
+ },
469
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0.00001 } }
470
+ },
471
+ {
472
+ "name": "online_wg_vec4_2x1024",
473
+ "attrs": { "axis": -1 },
474
+ "inputs": {
475
+ "x": {
476
+ "dtype": "float32",
477
+ "shape": [2, 1024],
478
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.07, "scale": 2.0 }
479
+ }
480
+ },
481
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 1024], "tolerance": 0.00001 } }
482
+ },
483
+ {
484
+ "name": "online_wg_scalar_odd_2x511",
485
+ "attrs": { "axis": -1 },
486
+ "inputs": {
487
+ "x": {
488
+ "dtype": "float32",
489
+ "shape": [2, 511],
490
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.11, "scale": 2.0 }
491
+ }
492
+ },
493
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 511], "tolerance": 0.00001 } }
494
+ },
495
+ {
496
+ "name": "online_wg_unaligned_even_2x510",
497
+ "attrs": { "axis": -1 },
498
+ "inputs": {
499
+ "x": {
500
+ "dtype": "float32",
501
+ "shape": [2, 510],
502
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.09, "scale": 2.0 }
503
+ }
504
+ },
505
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 510], "tolerance": 0.00001 } }
506
+ },
507
+ {
508
+ "name": "online_wg_batch_2048x64",
509
+ "attrs": { "axis": -1 },
510
+ "inputs": {
511
+ "x": {
512
+ "dtype": "float32",
513
+ "shape": [2048, 64],
514
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 2.0 }
515
+ }
516
+ },
517
+ "outputs": { "y": { "dtype": "float32", "shape": [2048, 64], "tolerance": 0.00001 } }
518
+ },
519
+ {
520
+ "name": "online_wg_all_negative_infinity_2x64",
521
+ "attrs": { "axis": -1 },
522
+ "inputs": { "x": { "dtype": "float32", "shape": [2, 64], "data": { "kind": "negativeInfinity" } } },
523
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 64], "tolerance": 0.000001, "allowNaN": true } }
524
+ },
525
+ {
526
+ "name": "online_wg_neg_inf_padding_mixed_2x40",
527
+ "attrs": { "axis": -1 },
528
+ "inputs": {
529
+ "x": {
530
+ "dtype": "float32",
531
+ "shape": [2, 40],
532
+ "data": { "kind": "cycle", "values": ["-Infinity", 0.5, 1.25, "-Infinity", -2.5] }
533
+ }
534
+ },
535
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 40], "tolerance": 0.00001 } }
536
+ },
537
+ {
538
+ "name": "online_wg_f16_2x128",
539
+ "attrs": { "axis": -1 },
540
+ "inputs": {
541
+ "x": {
542
+ "dtype": "float16",
543
+ "shape": [2, 128],
544
+ "data": { "kind": "fillFloat32", "sinStep": 0.21, "cosStep": 0.09, "scale": 2.0 }
545
+ }
546
+ },
547
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 128], "tolerance": 0.005 } }
548
+ },
549
+ {
550
+ "name": "dispatch_cliff_strided_rows_140000",
551
+ "provenance": {
552
+ "notes": "Non-last (strided) axis log-softmax over 140000 outer rows (>65535) forces the folded one-workgroup-per-row dispatch in the strided_3pass reduce_max/exp_sum passes (shared with Softmax). Validates the wg.x + wg.y*nwg.x reconstruction guarded by the compile-time STRIDED_ROWS const. Before the 2D fold the plan-time dispatch-limit guard throws."
553
+ },
554
+ "attrs": { "axis": 1 },
555
+ "inputs": {
556
+ "x": {
557
+ "dtype": "float32",
558
+ "shape": [70000, 4, 2],
559
+ "data": { "kind": "cycle", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] }
560
+ }
561
+ },
562
+ "outputs": { "y": { "dtype": "float32", "shape": [70000, 4, 2], "tolerance": 0.0001 } }
563
+ },
564
+ {
565
+ "name": "rank1_axis_minus1_f32",
566
+ "attrs": { "axis": -1 },
567
+ "inputs": {
568
+ "x": {
569
+ "dtype": "float32",
570
+ "shape": [128],
571
+ "data": { "kind": "fillFloat32", "sinStep": 0.041, "cosStep": 0.017, "scale": 3.0 }
572
+ }
573
+ },
574
+ "outputs": { "y": { "dtype": "float32", "shape": [128], "tolerance": 0.000001 } }
575
+ },
576
+ {
577
+ "name": "rank1_axis0_f32",
578
+ "attrs": { "axis": 0 },
579
+ "inputs": {
580
+ "x": {
581
+ "dtype": "float32",
582
+ "shape": [128],
583
+ "data": { "kind": "fillFloat32", "sinStep": 0.041, "cosStep": 0.017, "scale": 3.0 }
584
+ }
585
+ },
586
+ "outputs": { "y": { "dtype": "float32", "shape": [128], "tolerance": 0.000001 } }
587
+ },
588
+ {
589
+ "name": "f16_strided_axis1_rank3",
590
+ "attrs": { "axis": 1 },
591
+ "inputs": {
592
+ "x": {
593
+ "dtype": "float16",
594
+ "shape": [4, 8, 5],
595
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.07, "scale": 2.0 }
596
+ }
597
+ },
598
+ "outputs": { "y": { "dtype": "float16", "shape": [4, 8, 5], "tolerance": 0.005 } }
599
+ },
600
+ {
601
+ "name": "f16_longrow_split_1x65536",
602
+ "attrs": { "axis": -1 },
603
+ "inputs": {
604
+ "x": {
605
+ "dtype": "float16",
606
+ "shape": [1, 65536],
607
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.031, "scale": 2.0 }
608
+ }
609
+ },
610
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 65536], "tolerance": 0.005 } }
611
+ },
612
+ {
613
+ "name": "nan_in_input_propagates_rank2",
614
+ "attrs": { "axis": -1 },
615
+ "inputs": {
616
+ "x": {
617
+ "dtype": "float32",
618
+ "shape": [2, 4],
619
+ "data": { "kind": "values", "values": ["NaN", 1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 4.0] }
620
+ }
621
+ },
622
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 4], "tolerance": 0.000001, "allowNaN": true } }
623
+ },
624
+ {
625
+ "name": "longrow_split_positive_infinity_poisons_row_2x65536",
626
+ "attrs": { "axis": -1 },
627
+ "inputs": {
628
+ "x": {
629
+ "dtype": "float32",
630
+ "shape": [2, 65536],
631
+ "data": { "kind": "cycle", "values": ["Infinity", 0.5, 1.25, -2.5, 0.75, -1.0, 2.0] }
632
+ }
633
+ },
634
+ "outputs": {
635
+ "y": { "dtype": "float32", "shape": [2, 65536], "tolerance": 0.0001, "relTolerance": 0.0001, "allowNaN": true }
636
+ }
637
+ },
638
+ {
639
+ "name": "longrow_split_all_negative_infinity_row_nan_1x65536",
640
+ "attrs": { "axis": -1 },
641
+ "inputs": { "x": { "dtype": "float32", "shape": [1, 65536], "data": { "kind": "negativeInfinity" } } },
642
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 65536], "tolerance": 0.000001, "allowNaN": true } }
643
+ },
644
+ {
645
+ "name": "vec4_combine_positive_infinity_poisons_row_2x68",
646
+ "attrs": { "axis": -1 },
647
+ "inputs": {
648
+ "x": {
649
+ "dtype": "float32",
650
+ "shape": [2, 68],
651
+ "data": { "kind": "cycle", "values": ["Infinity", 0.25, -1.5, 2.0, 0.5, -0.75, 1.75, -2.25] }
652
+ }
653
+ },
654
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 68], "tolerance": 0.000001, "allowNaN": true } }
655
+ },
656
+ {
657
+ "name": "longrow_split_multirow_isolation_3x65536",
658
+ "attrs": { "axis": -1 },
659
+ "inputs": {
660
+ "x": {
661
+ "dtype": "float32",
662
+ "shape": [3, 65536],
663
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.023, "scale": 4.0, "offset": 100.0 }
664
+ }
665
+ },
666
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 65536], "tolerance": 0.0001, "relTolerance": 0.00001 } }
667
+ },
668
+ {
669
+ "name": "f16_longrow_split_positive_infinity_poisons_row_1x65536",
670
+ "attrs": { "axis": -1 },
671
+ "inputs": {
672
+ "x": {
673
+ "dtype": "float16",
674
+ "shape": [1, 65536],
675
+ "data": { "kind": "cycle", "values": ["Infinity", 0.5, 1.0, -1.5, 2.0, -0.5] }
676
+ }
677
+ },
678
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 65536], "tolerance": 0.005, "allowNaN": true } }
679
+ },
680
+ {
681
+ "name": "rank7_last_axis",
682
+ "attrs": { "axis": -1 },
683
+ "inputs": {
684
+ "x": {
685
+ "dtype": "float32",
686
+ "shape": [2, 1, 2, 1, 2, 1, 3],
687
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 2.0 }
688
+ }
689
+ },
690
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 1, 2, 1, 2, 1, 3], "tolerance": 0.000001 } }
691
+ },
692
+ {
693
+ "name": "strided_rowmax_scratch_over_128mib_f16_capacity_fallback",
694
+ "provenance": {
695
+ "notes": "Row-scratch capacity cliff: 33554433 strided rows need a 128 MiB+1 f32 rowMax buffer, so strided_3pass fails axisRowScratchFits and only the single-pass online fallback remains. f16 keeps the 64 MiB input inside the same spec-minimum storage-binding limit the f32 scratch overflows (and rejects the two f32-only strided routes), which is exactly the device situation the fallback exists for."
696
+ },
697
+ "attrs": { "axis": 0 },
698
+ "inputs": {
699
+ "x": {
700
+ "dtype": "float16",
701
+ "shape": [1, 33554433],
702
+ "data": { "kind": "cycle", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] }
703
+ }
704
+ },
705
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 33554433], "tolerance": 0.002 } }
706
+ },
707
+ {
708
+ "name": "rank8_last_axis",
709
+ "attrs": { "axis": -1 },
710
+ "inputs": {
711
+ "x": {
712
+ "dtype": "float32",
713
+ "shape": [2, 1, 2, 1, 2, 1, 2, 3],
714
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.21 }
715
+ }
716
+ },
717
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 1, 2, 1, 2, 1, 2, 3], "tolerance": 0.000001 } }
718
+ }
719
+ ]
720
+ }