Xenova HF Staff commited on
Commit
d5c68ce
·
verified ·
1 Parent(s): 2f706e2

sync 6fdf6301e2bb

Browse files
README.md CHANGED
@@ -48,7 +48,7 @@ Default values (overridable per request):
48
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
49
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
50
  - [`test.json`](build/webgpu/test.json) — correctness cases
51
- - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
52
  - [`lp-norm-divide.wgsl.jinja`](build/webgpu/lp-norm-divide.wgsl.jinja)
53
  - [`lp-norm-reduce.wgsl.jinja`](build/webgpu/lp-norm-reduce.wgsl.jinja)
54
  - [`lp-norm-split-combine.wgsl.jinja`](build/webgpu/lp-norm-split-combine.wgsl.jinja)
@@ -57,7 +57,7 @@ Default values (overridable per request):
57
  ## Use with `@huggingface/kernels`
58
 
59
  ```sh
60
- npm install --save-exact @huggingface/kernels@0.0.1-preview.2
61
  ```
62
 
63
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
 
48
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
49
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
50
  - [`test.json`](build/webgpu/test.json) — correctness cases
51
+ - [`bench.json`](build/webgpu/bench.json) — benchmark cases
52
  - [`lp-norm-divide.wgsl.jinja`](build/webgpu/lp-norm-divide.wgsl.jinja)
53
  - [`lp-norm-reduce.wgsl.jinja`](build/webgpu/lp-norm-reduce.wgsl.jinja)
54
  - [`lp-norm-split-combine.wgsl.jinja`](build/webgpu/lp-norm-split-combine.wgsl.jinja)
 
57
  ## Use with `@huggingface/kernels`
58
 
59
  ```sh
60
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.3
61
  ```
62
 
63
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
build/webgpu/lp-norm-reduce.wgsl.jinja CHANGED
@@ -15,9 +15,9 @@ const SPLIT: u32 = {{ split }}u;
15
  // tensor.
16
  @compute @workgroup_size(WG)
17
  fn main(
18
- @builtin(global_invocation_id) gid: vec3<u32>{% if segmented %},
 
19
  @builtin(workgroup_id) wid: vec3<u32>,
20
- {% else %},
21
  {% endif %}
22
  @builtin(num_workgroups) nwg: vec3<u32>
23
  ) {
 
15
  // tensor.
16
  @compute @workgroup_size(WG)
17
  fn main(
18
+ @builtin(global_invocation_id) gid: vec3<u32>,
19
+ {% if segmented %}
20
  @builtin(workgroup_id) wid: vec3<u32>,
 
21
  {% endif %}
22
  @builtin(num_workgroups) nwg: vec3<u32>
23
  ) {
build/webgpu/manifest.json CHANGED
@@ -22,9 +22,8 @@
22
  },
23
  "when": ["ranks.output == ranks.input", "numel(shapes.input) == numel(shapes.output)", "f16Ok(dtypes.T)"],
24
  "bindings": {
25
- "input": { "buffer": "read-only-storage", "elementType": "$scalar" },
26
  "params": {
27
- "buffer": "uniform",
28
  "struct": [
29
  { "name": "rows", "type": "u32", "value": "axisRows" },
30
  { "name": "dim", "type": "u32", "value": "axisDim" },
@@ -32,12 +31,11 @@
32
  { "name": "p", "type": "u32", "value": "attrs.p" }
33
  ]
34
  },
35
- "input_2": { "name": "input", "buffer": "read-only-storage", "elementType": "$divElem" },
36
  "rowNorms": { "buffer": "read-only-storage", "elementType": "f32" },
37
- "output": { "buffer": "storage", "elementType": "$divElem" },
38
- "params_3": {
39
  "name": "params",
40
- "buffer": "uniform",
41
  "struct": [
42
  { "name": "count", "type": "u32", "value": "divideCount" },
43
  { "name": "dim", "type": "u32", "value": "axisDim" },
@@ -80,10 +78,9 @@
80
  "id": "combine",
81
  "name": "LpNormalization.SplitCombine",
82
  "shader": "lp-norm-split-combine.wgsl.jinja",
83
- "derive": {},
84
  "bindings": [
85
  { "name": "partials", "buffer": "read-only-storage", "elementType": "f32" },
86
- { "name": "rowNorms", "buffer": "storage", "elementType": "f32" },
87
  {
88
  "name": "params",
89
  "struct": [
@@ -102,7 +99,7 @@
102
  "id": "divide",
103
  "name": "LpNormalization.Divide",
104
  "shader": "lp-norm-divide.wgsl.jinja",
105
- "bindings": ["input_2", "rowNorms", "output", "params_3"],
106
  "dispatch": {
107
  "x": "min(ceilDiv((divideCount), (workgroupSize)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
108
  "y": 1,
@@ -140,7 +137,7 @@
140
  "id": "divide",
141
  "name": "LpNormalization.Divide",
142
  "shader": "lp-norm-divide.wgsl.jinja",
143
- "bindings": ["input_2", "rowNorms", "output", "params_3"],
144
  "dispatch": {
145
  "x": "min(ceilDiv((divideCount), (workgroupSize)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
146
  "y": 1,
@@ -162,8 +159,6 @@
162
  "derive": {
163
  "modeSpec": "\"lp\"",
164
  "vec4": true,
165
- "scalar": "dtypes.T",
166
- "usesF16Spec": "dtypes.T == \"f16\"",
167
  "hidden": "dim(shapes.input, -1)",
168
  "wg": "min(workgroupSize, pow2ceil(dim(shapes.input, -1) / 4))",
169
  "p": "attrs.p",
@@ -190,8 +185,7 @@
190
  "x": "min(numel(shapes.input) / dim(shapes.input, -1), 65535)",
191
  "y": "ceilDiv(numel(shapes.input) / dim(shapes.input, -1), 65535)",
192
  "z": 1
193
- },
194
- "subgroupCollectivesWidth": "portable"
195
  }
196
  ]
197
  },
@@ -208,8 +202,6 @@
208
  "derive": {
209
  "modeSpec": "\"lp\"",
210
  "vec4": false,
211
- "scalar": "dtypes.T",
212
- "usesF16Spec": "dtypes.T == \"f16\"",
213
  "hidden": "dim(shapes.input, -1)",
214
  "wg": "min(workgroupSize, pow2ceil(dim(shapes.input, -1)))",
215
  "p": "attrs.p",
@@ -236,8 +228,7 @@
236
  "x": "min(numel(shapes.input) / dim(shapes.input, -1), 65535)",
237
  "y": "ceilDiv(numel(shapes.input) / dim(shapes.input, -1), 65535)",
238
  "z": 1
239
- },
240
- "subgroupCollectivesWidth": "portable"
241
  }
242
  ]
243
  }
 
22
  },
23
  "when": ["ranks.output == ranks.input", "numel(shapes.input) == numel(shapes.output)", "f16Ok(dtypes.T)"],
24
  "bindings": {
25
+ "input": { "elementType": "$scalar" },
26
  "params": {
 
27
  "struct": [
28
  { "name": "rows", "type": "u32", "value": "axisRows" },
29
  { "name": "dim", "type": "u32", "value": "axisDim" },
 
31
  { "name": "p", "type": "u32", "value": "attrs.p" }
32
  ]
33
  },
34
+ "input_axis": { "name": "input", "elementType": "$divElem" },
35
  "rowNorms": { "buffer": "read-only-storage", "elementType": "f32" },
36
+ "output": { "elementType": "$divElem" },
37
+ "params_axis": {
38
  "name": "params",
 
39
  "struct": [
40
  { "name": "count", "type": "u32", "value": "divideCount" },
41
  { "name": "dim", "type": "u32", "value": "axisDim" },
 
78
  "id": "combine",
79
  "name": "LpNormalization.SplitCombine",
80
  "shader": "lp-norm-split-combine.wgsl.jinja",
 
81
  "bindings": [
82
  { "name": "partials", "buffer": "read-only-storage", "elementType": "f32" },
83
+ { "name": "rowNorms", "elementType": "f32" },
84
  {
85
  "name": "params",
86
  "struct": [
 
99
  "id": "divide",
100
  "name": "LpNormalization.Divide",
101
  "shader": "lp-norm-divide.wgsl.jinja",
102
+ "bindings": ["input_axis", "rowNorms", "output", "params_axis"],
103
  "dispatch": {
104
  "x": "min(ceilDiv((divideCount), (workgroupSize)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
105
  "y": 1,
 
137
  "id": "divide",
138
  "name": "LpNormalization.Divide",
139
  "shader": "lp-norm-divide.wgsl.jinja",
140
+ "bindings": ["input_axis", "rowNorms", "output", "params_axis"],
141
  "dispatch": {
142
  "x": "min(ceilDiv((divideCount), (workgroupSize)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
143
  "y": 1,
 
159
  "derive": {
160
  "modeSpec": "\"lp\"",
161
  "vec4": true,
 
 
162
  "hidden": "dim(shapes.input, -1)",
163
  "wg": "min(workgroupSize, pow2ceil(dim(shapes.input, -1) / 4))",
164
  "p": "attrs.p",
 
185
  "x": "min(numel(shapes.input) / dim(shapes.input, -1), 65535)",
186
  "y": "ceilDiv(numel(shapes.input) / dim(shapes.input, -1), 65535)",
187
  "z": 1
188
+ }
 
189
  }
190
  ]
191
  },
 
202
  "derive": {
203
  "modeSpec": "\"lp\"",
204
  "vec4": false,
 
 
205
  "hidden": "dim(shapes.input, -1)",
206
  "wg": "min(workgroupSize, pow2ceil(dim(shapes.input, -1)))",
207
  "p": "attrs.p",
 
228
  "x": "min(numel(shapes.input) / dim(shapes.input, -1), 65535)",
229
  "y": "ceilDiv(numel(shapes.input) / dim(shapes.input, -1), 65535)",
230
  "z": 1
231
+ }
 
232
  }
233
  ]
234
  }
build/webgpu/metadata.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "ai.onnx.LpNormalization",
3
- "id": "_ai_onnx_lpnormalization_webgpu_7afe9b5",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
@@ -9,16 +9,16 @@
9
  "files": {
10
  "bench.json": "Zqjg8X2eQ7EWdSXd2uSW+U227BKDhM9aUvyzA2Fd75o=",
11
  "lp-norm-divide.wgsl.jinja": "bIMXO9qn/IyThJQr5tapg04aNYkQqYt6LK/yz9D9ZBo=",
12
- "lp-norm-reduce.wgsl.jinja": "Yu7Q9BM+uAAsEfA4VC1PQR7VOxdTyn+A+mjIryfLJwc=",
13
  "lp-norm-split-combine.wgsl.jinja": "PzQVkT5bUQ3+l2lqn3MgA2P0MbokIMa8Q6fVT4lFq9Q=",
14
- "manifest.json": "EVQo8vZnPKz7vHj4+ZebBiS7UI2rAlpagBmiNdNEp6c=",
15
- "norm-row-stats.wgsl.jinja": "iwey3jfLc6FXtfY4bBqo5+YG3ALWvo0RHLn12HHeJ0Q=",
16
- "test.json": "7Vll6iQ9EAVx7E3pLedW2baT6dCaQLOfgy3qgY3xzyQ="
17
  }
18
  },
19
- "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
20
  "webgpu": {
21
- "manifestSpec": "2.0",
22
  "variants": {
23
  "axis_splitk": ["lp-norm-divide.wgsl.jinja", "lp-norm-reduce.wgsl.jinja", "lp-norm-split-combine.wgsl.jinja"],
24
  "axis": ["lp-norm-divide.wgsl.jinja", "lp-norm-reduce.wgsl.jinja"],
 
1
  {
2
  "name": "ai.onnx.LpNormalization",
3
+ "id": "_ai_onnx_lpnormalization_webgpu_9e7a6b8",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
 
9
  "files": {
10
  "bench.json": "Zqjg8X2eQ7EWdSXd2uSW+U227BKDhM9aUvyzA2Fd75o=",
11
  "lp-norm-divide.wgsl.jinja": "bIMXO9qn/IyThJQr5tapg04aNYkQqYt6LK/yz9D9ZBo=",
12
+ "lp-norm-reduce.wgsl.jinja": "S2wrYWXN0Mbb6OXcSlaxOnMQXmQ6k0R0WgPai4nXjZA=",
13
  "lp-norm-split-combine.wgsl.jinja": "PzQVkT5bUQ3+l2lqn3MgA2P0MbokIMa8Q6fVT4lFq9Q=",
14
+ "manifest.json": "sTPEZdTEkXALooJrYq/AV8nXbfxC5uyAOG1GsngDYMM=",
15
+ "norm-row-stats.wgsl.jinja": "iUons1Qj/Og11tYG6yRsNsKi3sdLtXC454tF+T2b8wg=",
16
+ "test.json": "W80CQp2OhX4ZWC8hdozFfQcDfmcpt4ubQfRGeVk5z1I="
17
  }
18
  },
19
+ "provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
20
  "webgpu": {
21
+ "manifestSpec": "2.1",
22
  "variants": {
23
  "axis_splitk": ["lp-norm-divide.wgsl.jinja", "lp-norm-reduce.wgsl.jinja", "lp-norm-split-combine.wgsl.jinja"],
24
  "axis": ["lp-norm-divide.wgsl.jinja", "lp-norm-reduce.wgsl.jinja"],
build/webgpu/norm-row-stats.wgsl.jinja CHANGED
@@ -1,15 +1,6 @@
1
- {% if usesF16Spec %}
2
- enable f16;
3
- {% endif %}
4
- {% set combineSubgroups = combineSubgroups %}
5
- {% set scalarIo = scalarIo if scalarIo is defined else false %}
6
- {% set packedBf16Embedding = packedBf16Embedding if packedBf16Embedding is defined else false %}
7
  {% set lpOrder = p if p is defined else 0 %}
8
- {% set rmsChainNorm = rmsChainNorm if rmsChainNorm is defined else false %}
9
- {% set hiddenPairs = hiddenPairs | default(0) %}
10
- {% set numRows = numRows | default(0) %}
11
- {% set epsilon = epsilon | default("0.0") %}
12
- {% set epsilon2 = epsilon2 | default("0.0") %}
13
  {% set reduceThreadParameters = ", sg_lane: u32, sg_id: u32, num_sg: u32"
14
  if combineSubgroups else ", tid: u32" %}
15
  {% set reduceThreadArguments = ", sg_lane, sg_id, num_sg"
@@ -34,52 +25,7 @@ const HIDDEN: u32 = {{ hidden }}u;
34
  {% if vec4 %}
35
  const HIDDEN_V: u32 = {{ hiddenVec }}u;
36
  {% endif %}
37
- {% if packedBf16Embedding %}
38
- const HIDDEN_PAIRS: u32 = {{ hiddenPairs }}u;
39
- const NUM_ROWS: u32 = {{ numRows }}u;
40
- {% endif %}
41
  const WG: u32 = {{ wg }}u;
42
- {% if rmsChainNorm %}
43
- const EPSILON2: f32 = {{ epsilon2 }};
44
- {% endif %}
45
-
46
- {% if packedBf16Embedding %}
47
- {% if vec4 %}
48
- fn unpack_bf16_pair(word: u32) -> vec2<f32> {
49
- let bits = vec2<u32>(word & 0xffffu, word >> 16u);
50
- return bitcast<vec2<f32>>(bits << vec2<u32>(16u));
51
- }
52
- {% endif %}
53
-
54
- {% if not vec4 %}
55
- fn embedding_scalar(source_row: u32, hidden: u32) -> f32 {
56
- if (source_row >= NUM_ROWS) {
57
- return 0.0;
58
- }
59
- let word = x[source_row * HIDDEN_PAIRS + (hidden >> 1u)];
60
- let bits = select(word & 0xffffu, word >> 16u, (hidden & 1u) != 0u);
61
- return bitcast<f32>(bits << 16u);
62
- }
63
- {% endif %}
64
-
65
- {% if vec4 %}
66
- fn embedding_vec4(source_row: u32, hidden_vec: u32) -> vec4<f32> {
67
- if (source_row >= NUM_ROWS) {
68
- return vec4<f32>(0.0);
69
- }
70
- let base = source_row * HIDDEN_PAIRS + hidden_vec * 2u;
71
- let low = unpack_bf16_pair(x[base]);
72
- let high = unpack_bf16_pair(x[base + 1u]);
73
- return vec4<f32>(low, high);
74
- }
75
- {% endif %}
76
- {% endif %}
77
-
78
- {% if vec4 and scalarIo %}
79
- fn load_vec4(index: u32) -> vec4<f32> {
80
- return vec4<f32>(x[index], x[index + 1u], x[index + 2u], x[index + 3u]);
81
- }
82
- {% endif %}
83
 
84
  {% if combineSubgroups %}
85
  var<workgroup> sg_partials: array<f32, WG>;
@@ -133,31 +79,16 @@ fn main(
133
  return;
134
  }
135
  let tid = lid.x;
136
- {% if packedBf16Embedding %}
137
- let source_row = indices[row];
138
- {% if vec4 %}
139
  let base = row * HIDDEN_V;
140
  {% else %}
141
  let base = row * HIDDEN;
142
  {% endif %}
143
- {% elif vec4 and not scalarIo %}
144
- let base = row * HIDDEN_V;
145
- {% else %}
146
- let base = row * HIDDEN;
147
- {% endif %}
148
-
149
 
150
  var acc = 0.0;
151
  {% if vec4 %}
152
  for (var i = tid; i < HIDDEN_V; i = i + WG) {
153
- {% if packedBf16Embedding %}
154
- let v = embedding_vec4(source_row, i);
155
- embedding_out[base + i] = v;
156
- {% elif scalarIo %}
157
- let v = load_vec4(base + i * 4u);
158
- {% else %}
159
- let v = vec4<f32>(x[base + i]);
160
- {% endif %}
161
  {% if lpOrder == 1 %}
162
  let a = abs(v);
163
  acc = acc + a.x + a.y + a.z + a.w;
@@ -167,12 +98,7 @@ fn main(
167
  }
168
  {% else %}
169
  for (var i = tid; i < HIDDEN; i = i + WG) {
170
- {% if packedBf16Embedding %}
171
- let v = embedding_scalar(source_row, i);
172
- embedding_out[base + i] = v;
173
- {% else %}
174
  let v = f32(x[base + i]);
175
- {% endif %}
176
  {% if lpOrder == 1 %}
177
  acc = acc + abs(v);
178
  {% else %}
@@ -189,47 +115,17 @@ fn main(
189
  let norm = total;
190
  {% endif %}
191
 
192
- {% if rmsChainNorm %}
193
- var acc2 = 0.0;
194
- {% endif %}
195
  {% if vec4 %}
196
  for (var i = tid; i < HIDDEN_V; i = i + WG) {
197
- {% if packedBf16Embedding %}
198
  let idx = base + i;
199
- let v = embedding_vec4(source_row, i);
200
- {% elif scalarIo %}
201
- let idx = base + i * 4u;
202
- let v = load_vec4(idx);
203
- {% else %}
204
- let idx = base + i;
205
- let v = vec4<f32>(x[idx]);
206
- {% endif %}
207
  let normalized = select(v / vec4<f32>(norm), vec4<f32>(0.0), vec4<bool>(norm == 0.0));
208
  y[idx] = {{ vecType }}(normalized);
209
  }
210
- {% if rmsChainNorm %}
211
-
212
- // The chained second norm reads the residual row this loop just stored. This
213
- // barrier completes those stores and any preceding shared-scratch use before
214
- // the next reduction reuses its scratch; each lane then re-reads only the
215
- // elements it wrote itself.
216
- workgroupBarrier();
217
- let total2 = reduce_scalar(acc2{{ reduceThreadArguments }});
218
- let inv2 = inverseSqrt(total2 / f32(HIDDEN) + EPSILON2);
219
- for (var i = tid; i < HIDDEN_V; i = i + WG) {
220
- let idx = base + i;
221
- let hv = vec4<f32>(y[idx]);
222
- normed2[idx] = {{ vecType }}(hv * inv2 * vec4<f32>(scale2[i]));
223
- }
224
- {% endif %}
225
  {% else %}
226
  for (var i = tid; i < HIDDEN; i = i + WG) {
227
  let idx = base + i;
228
- {% if packedBf16Embedding %}
229
- let v = embedding_scalar(source_row, i);
230
- {% else %}
231
  let v = f32(x[idx]);
232
- {% endif %}
233
  let normalized = select(v / norm, 0.0, norm == 0.0);
234
  y[idx] = {{ scalar }}(normalized);
235
  }
 
1
+ {% set scalarIo = false %}
2
+ {% set packedF32 = "vec4<f32>" %}
 
 
 
 
3
  {% set lpOrder = p if p is defined else 0 %}
 
 
 
 
 
4
  {% set reduceThreadParameters = ", sg_lane: u32, sg_id: u32, num_sg: u32"
5
  if combineSubgroups else ", tid: u32" %}
6
  {% set reduceThreadArguments = ", sg_lane, sg_id, num_sg"
 
25
  {% if vec4 %}
26
  const HIDDEN_V: u32 = {{ hiddenVec }}u;
27
  {% endif %}
 
 
 
 
28
  const WG: u32 = {{ wg }}u;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  {% if combineSubgroups %}
31
  var<workgroup> sg_partials: array<f32, WG>;
 
79
  return;
80
  }
81
  let tid = lid.x;
82
+ {% if vec4 and not scalarIo %}
 
 
83
  let base = row * HIDDEN_V;
84
  {% else %}
85
  let base = row * HIDDEN;
86
  {% endif %}
 
 
 
 
 
 
87
 
88
  var acc = 0.0;
89
  {% if vec4 %}
90
  for (var i = tid; i < HIDDEN_V; i = i + WG) {
91
+ let v = {{ packedF32 }}(x[base + i]);
 
 
 
 
 
 
 
92
  {% if lpOrder == 1 %}
93
  let a = abs(v);
94
  acc = acc + a.x + a.y + a.z + a.w;
 
98
  }
99
  {% else %}
100
  for (var i = tid; i < HIDDEN; i = i + WG) {
 
 
 
 
101
  let v = f32(x[base + i]);
 
102
  {% if lpOrder == 1 %}
103
  acc = acc + abs(v);
104
  {% else %}
 
115
  let norm = total;
116
  {% endif %}
117
 
 
 
 
118
  {% if vec4 %}
119
  for (var i = tid; i < HIDDEN_V; i = i + WG) {
 
120
  let idx = base + i;
121
+ let v = {{ packedF32 }}(x[idx]);
 
 
 
 
 
 
 
122
  let normalized = select(v / vec4<f32>(norm), vec4<f32>(0.0), vec4<bool>(norm == 0.0));
123
  y[idx] = {{ vecType }}(normalized);
124
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  {% else %}
126
  for (var i = tid; i < HIDDEN; i = i + WG) {
127
  let idx = base + i;
 
 
 
128
  let v = f32(x[idx]);
 
129
  let normalized = select(v / norm, 0.0, norm == 0.0);
130
  y[idx] = {{ scalar }}(normalized);
131
  }
build/webgpu/test.json CHANGED
@@ -225,7 +225,7 @@
225
  "outputs": { "output": { "dtype": "float16", "shape": [2, 128], "tolerance": 0.00002, "relTolerance": 0.002 } }
226
  },
227
  {
228
- "name": "f16_p1_last_axis_constant200_norm_scale_lock",
229
  "provenance": {
230
  "notes": "Each 128-element row contains 200, so its L1 norm is 25,600 and every normalized value is exactly 2^-7 in float16. A 1e-6 tolerance makes an incorrect divisor observable."
231
  },
 
225
  "outputs": { "output": { "dtype": "float16", "shape": [2, 128], "tolerance": 0.00002, "relTolerance": 0.002 } }
226
  },
227
  {
228
+ "name": "f16_p1_last_axis_constant200_norm_scale",
229
  "provenance": {
230
  "notes": "Each 128-element row contains 200, so its L1 norm is 25,600 and every normalized value is exactly 2^-7 in float16. A 1e-6 tolerance makes an incorrect divisor observable."
231
  },