| { |
| "domain": "ai.onnx", |
| "name": "MeanVarianceNormalization", |
| "sinceVersion": 13, |
| "description": "Normalizes each group as `(X - mean) / sqrt(variance)`, reducing over `axes` (default `[0, 2, 3]`).", |
| "inputs": [{ "role": "X", "dtype": "T", "description": "Input tensor to normalize." }], |
| "outputs": [ |
| { |
| "role": "Y", |
| "dtype": "T", |
| "rank": "ranks.X", |
| "description": "Normalized tensor with the same shape as `X`.", |
| "shape": "shapes.X" |
| } |
| ], |
| "attributes": { "axes": [0, 2, 3] }, |
| "attributeDescriptions": { "axes": "Axes that share a mean and variance; negative values count from the back." }, |
| "typeConstraints": { "T": ["float32", "float16"] }, |
| "args": { |
| "x": { "kind": "tensor", "semantic": "X", "role": "input" }, |
| "y": { "kind": "tensor", "semantic": "Y", "role": "output" } |
| }, |
| "tunables": { |
| "WORKGROUP_SIZE": 256, |
| "SERIAL_WORKGROUP_SIZE": 256, |
| "SERIAL_TINY_WORKGROUP_SIZE": 64, |
| "SERIAL_MAX_REDUCTION": 128, |
| "SERIAL_MIN_ROWS": 256, |
| "TREE_MEDIUM_WORKGROUP_SIZE": 64, |
| "VEC4_MIN_REDUCTION": 8, |
| "FLAT_SPLIT_MIN_ELEMENTS": 65536, |
| "FLAT_SPLIT_TARGET_ELEMENTS": 4096, |
| "MAX_FLAT_SPLITS": 256 |
| }, |
| "derive": { |
| "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)", |
| "foldedDispatchCapacity": "device.limits.maxComputeWorkgroupsPerDimension * device.limits.maxComputeWorkgroupsPerDimension", |
| "shapeContract": "ranks.X >= 3 and ranks.Y == ranks.X and sameShape(shapes.Y, shapes.X) and f16Ok(dtypes.T)", |
| "reduceCount": "(dim(shapes.X, 0) if hasAxis(attrs.axes, 0, ranks.X) else 1) * (dim(shapes.X, 1) if hasAxis(attrs.axes, 1, ranks.X) else 1) * (dim(shapes.X, 2) if hasAxis(attrs.axes, 2, ranks.X) else 1) * (dim(shapes.X, 3) if ranks.X >= 4 and hasAxis(attrs.axes, 3, ranks.X) else 1) * (dim(shapes.X, 4) if ranks.X >= 5 and hasAxis(attrs.axes, 4, ranks.X) else 1) * (dim(shapes.X, 5) if ranks.X >= 6 and hasAxis(attrs.axes, 5, ranks.X) else 1)", |
| "rowCount": "numel(shapes.X) / max(1, reduceCount)", |
| "allAxesReduced": "hasAxis(attrs.axes, 0, ranks.X) and hasAxis(attrs.axes, 1, ranks.X) and hasAxis(attrs.axes, 2, ranks.X) and (ranks.X < 4 or hasAxis(attrs.axes, 3, ranks.X)) and (ranks.X < 5 or hasAxis(attrs.axes, 4, ranks.X)) and (ranks.X < 6 or hasAxis(attrs.axes, 5, ranks.X))", |
| "vec4Eligible": "((ranks.X == 3 and hasAxis(attrs.axes, 2, 3) and (dim(shapes.X, 2) % 4 == 0 or (hasAxis(attrs.axes, 1, 3) and dim(shapes.X, 1) * dim(shapes.X, 2) % 4 == 0) or (hasAxis(attrs.axes, 0, 3) and hasAxis(attrs.axes, 1, 3) and numel(shapes.X) % 4 == 0))) or (ranks.X == 4 and hasAxis(attrs.axes, 3, 4) and (dim(shapes.X, 3) % 4 == 0 or (hasAxis(attrs.axes, 2, 4) and dim(shapes.X, 2) * dim(shapes.X, 3) % 4 == 0) or (hasAxis(attrs.axes, 1, 4) and hasAxis(attrs.axes, 2, 4) and dim(shapes.X, 1) * dim(shapes.X, 2) * dim(shapes.X, 3) % 4 == 0) or (hasAxis(attrs.axes, 0, 4) and hasAxis(attrs.axes, 1, 4) and hasAxis(attrs.axes, 2, 4) and numel(shapes.X) % 4 == 0))) or (ranks.X == 5 and hasAxis(attrs.axes, 4, 5) and (dim(shapes.X, 4) % 4 == 0 or (hasAxis(attrs.axes, 3, 5) and dim(shapes.X, 3) * dim(shapes.X, 4) % 4 == 0) or (hasAxis(attrs.axes, 2, 5) and hasAxis(attrs.axes, 3, 5) and dim(shapes.X, 2) * dim(shapes.X, 3) * dim(shapes.X, 4) % 4 == 0) or (hasAxis(attrs.axes, 1, 5) and hasAxis(attrs.axes, 2, 5) and hasAxis(attrs.axes, 3, 5) and dim(shapes.X, 1) * dim(shapes.X, 2) * dim(shapes.X, 3) * dim(shapes.X, 4) % 4 == 0) or (allAxesReduced and numel(shapes.X) % 4 == 0))) or (ranks.X == 6 and hasAxis(attrs.axes, 5, 6) and (dim(shapes.X, 5) % 4 == 0 or (hasAxis(attrs.axes, 4, 6) and dim(shapes.X, 4) * dim(shapes.X, 5) % 4 == 0) or (hasAxis(attrs.axes, 3, 6) and hasAxis(attrs.axes, 4, 6) and dim(shapes.X, 3) * dim(shapes.X, 4) * dim(shapes.X, 5) % 4 == 0) or (hasAxis(attrs.axes, 2, 6) and hasAxis(attrs.axes, 3, 6) and hasAxis(attrs.axes, 4, 6) and dim(shapes.X, 2) * dim(shapes.X, 3) * dim(shapes.X, 4) * dim(shapes.X, 5) % 4 == 0) or (hasAxis(attrs.axes, 1, 6) and hasAxis(attrs.axes, 2, 6) and hasAxis(attrs.axes, 3, 6) and hasAxis(attrs.axes, 4, 6) and dim(shapes.X, 1) * dim(shapes.X, 2) * dim(shapes.X, 3) * dim(shapes.X, 4) * dim(shapes.X, 5) % 4 == 0) or (allAxesReduced and numel(shapes.X) % 4 == 0))))", |
| "maxWorkgroupSize": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)", |
| "minSubgroupSize": "device.adapterInfo.subgroupMinSize if has(device.adapterInfo, \"subgroupMinSize\") else 1", |
| "useSubgroups": "device.features.has(\"subgroups\") and has(device.adapterInfo, \"subgroupMinSize\") and minSubgroupSize > 0", |
| "scalarWorkgroupSize": "min(maxWorkgroupSize, tunables.TREE_MEDIUM_WORKGROUP_SIZE) if not useSubgroups and reduceCount > tunables.TREE_MEDIUM_WORKGROUP_SIZE and reduceCount <= 2 * tunables.TREE_MEDIUM_WORKGROUP_SIZE else min(maxWorkgroupSize, max(1, pow2ceil(reduceCount)))", |
| "vectorWorkgroupSize": "min(maxWorkgroupSize, max(1, pow2ceil(ceilDiv(reduceCount, 4))))", |
| "serialWorkgroupSize": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX, (tunables.SERIAL_TINY_WORKGROUP_SIZE if reduceCount <= 4 else tunables.SERIAL_WORKGROUP_SIZE))", |
| "rowDispatchFits": "rowCount <= foldedDispatchCapacity", |
| "serialDispatchFits": "ceilDiv(rowCount, serialWorkgroupSize) <= foldedDispatchCapacity", |
| "applyDispatchFits": "ceilDiv(numel(shapes.Y), maxWorkgroupSize) <= foldedDispatchCapacity", |
| "scalarStorageFits": "scalarWorkgroupSize * 8 <= device.limits.maxComputeWorkgroupStorageSize", |
| "vectorStorageFits": "vectorWorkgroupSize * 8 <= device.limits.maxComputeWorkgroupStorageSize", |
| "flatSplit": "min(tunables.MAX_FLAT_SPLITS, pow2ceil(ceilDiv(numel(shapes.X), tunables.FLAT_SPLIT_TARGET_ELEMENTS)))", |
| "flatScratchBytes": "flatSplit * 8", |
| "flatPathFits": "flatSplit <= device.limits.maxComputeWorkgroupsPerDimension and flatScratchBytes <= device.limits.maxStorageBufferBindingSize and flatScratchBytes <= device.limits.maxBufferSize and maxWorkgroupSize * 8 <= device.limits.maxComputeWorkgroupStorageSize and applyDispatchFits" |
| }, |
| "bindingSets": { |
| "rows": [ |
| { |
| "name": "x", |
| "arg": "x", |
| "semantic": "X", |
| "buffer": { "type": "read-only-storage" }, |
| "elementType": "$ioElement" |
| }, |
| { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$ioElement" }, |
| { |
| "name": "params", |
| "semantic": "kernel.params", |
| "buffer": { "type": "uniform" }, |
| "struct": { "name": "Params", "fields": [{ "name": "rows", "type": "u32", "value": "rowCount" }] } |
| } |
| ], |
| "flatPartials": [ |
| { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" }, |
| { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "vec2<f32>" }, |
| { |
| "name": "params", |
| "semantic": "kernel.params", |
| "buffer": { "type": "uniform" }, |
| "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.X)" }] } |
| } |
| ], |
| "flatCombine": [ |
| { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" }, |
| { |
| "name": "partials", |
| "semantic": "partials", |
| "buffer": { "type": "read-only-storage" }, |
| "elementType": "vec2<f32>" |
| }, |
| { "name": "stats", "semantic": "stats", "buffer": { "type": "storage" }, "elementType": "f32", "length": 2 }, |
| { |
| "name": "params", |
| "semantic": "kernel.params", |
| "buffer": { "type": "uniform" }, |
| "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.X)" }] } |
| } |
| ], |
| "flatApply": [ |
| { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" }, |
| { |
| "name": "stats", |
| "semantic": "stats", |
| "buffer": { "type": "read-only-storage" }, |
| "elementType": "f32", |
| "length": 2 |
| }, |
| { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" }, |
| { |
| "name": "params", |
| "semantic": "kernel.params", |
| "buffer": { "type": "uniform" }, |
| "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.Y)" }] } |
| } |
| ], |
| "noopParams": [ |
| { |
| "name": "params", |
| "semantic": "kernel.params", |
| "buffer": { "type": "uniform" }, |
| "struct": { "name": "Params", "fields": [{ "name": "dummy", "type": "u32", "value": 0 }] } |
| } |
| ] |
| }, |
| "variants": [ |
| { |
| "id": "empty_noop", |
| "priority": 200, |
| "when": ["shapeContract", "numel(shapes.X) == 0"], |
| "passes": [ |
| { |
| "id": "noop", |
| "name": "MeanVarianceNormalization.Empty", |
| "shader": "noop.wgsl.jinja", |
| "bindings": "noopParams", |
| "dispatch": { "x": 0 } |
| } |
| ] |
| }, |
| { |
| "id": "all_axes_flat_split", |
| "priority": 120, |
| "when": ["shapeContract", "numel(shapes.X) > 0", "allAxesReduced", "numel(shapes.X) >= tunables.FLAT_SPLIT_MIN_ELEMENTS", "flatPathFits"], |
| "constants": { "scalar": "dtypes.T" }, |
| "intermediates": [ |
| { "id": "partials", "dtype": "float32", "shape": "[flatSplit, 2]" }, |
| { "id": "stats", "dtype": "float32", "shape": "[2]" } |
| ], |
| "passes": [ |
| { |
| "id": "partials", |
| "name": "MeanVarianceNormalization.FlatPartials", |
| "source": { |
| "shader": "norm-flat-splitk-partials.wgsl.jinja", |
| "inputs": { "workgroupSize": "maxWorkgroupSize", "split": "flatSplit", "usesF16": "dtypes.T == \"f16\"" } |
| }, |
| "bindings": "flatPartials", |
| "dispatch": { "workgroups": "flatSplit" } |
| }, |
| { |
| "id": "combine", |
| "name": "MeanVarianceNormalization.FlatCombine", |
| "source": { |
| "shader": "norm-flat-splitk-combine.wgsl.jinja", |
| "inputs": { "split": "flatSplit", "usesF16": "dtypes.T == \"f16\"" } |
| }, |
| "bindings": "flatCombine", |
| "dispatch": { "workgroups": 1 } |
| }, |
| { |
| "id": "apply", |
| "name": "MeanVarianceNormalization.FlatApply", |
| "source": { |
| "shader": "norm-flat-apply.wgsl.jinja", |
| "inputs": { "workgroupSize": "maxWorkgroupSize", "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" } |
| }, |
| "bindings": "flatApply", |
| "dispatch": { "threads": "numel(shapes.Y)", "workgroupSize": "maxWorkgroupSize" } |
| } |
| ] |
| }, |
| { |
| "id": "serial_rows", |
| "priority": 115, |
| "when": ["shapeContract", "numel(shapes.X) > 0", "reduceCount <= tunables.SERIAL_MAX_REDUCTION", "rowCount >= tunables.SERIAL_MIN_ROWS", "serialDispatchFits"], |
| "constants": { "scalar": "dtypes.T", "ioElement": "dtypes.T" }, |
| "passes": [ |
| { |
| "id": "main", |
| "name": "MeanVarianceNormalization.SerialRows", |
| "source": { |
| "shader": "mean-variance-normalization-serial-rows.wgsl.jinja", |
| "inputs": { |
| "xShape": "shapes.X", |
| "reduce": ["hasAxis(attrs.axes, 0, ranks.X)", "hasAxis(attrs.axes, 1, ranks.X)", "hasAxis(attrs.axes, 2, ranks.X)", "hasAxis(attrs.axes, 3, ranks.X)", "hasAxis(attrs.axes, 4, ranks.X)", "hasAxis(attrs.axes, 5, ranks.X)"], |
| "reduceCount": "reduceCount", |
| "workgroupSize": "serialWorkgroupSize", |
| "scalar": "dtypes.T", |
| "usesF16": "dtypes.T == \"f16\"" |
| } |
| }, |
| "bindings": "rows", |
| "dispatch": { "threads": "rowCount", "workgroupSize": "serialWorkgroupSize" } |
| } |
| ] |
| }, |
| { |
| "id": "cooperative_vec4", |
| "priority": 110, |
| "when": ["shapeContract", "numel(shapes.X) > 0", "reduceCount >= tunables.VEC4_MIN_REDUCTION", "vec4Eligible", "rowDispatchFits", "vectorStorageFits"], |
| "constants": { |
| "scalar": "dtypes.T", |
| "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"", |
| "ioElement": "\"vec4<\" ~ dtypes.T ~ \">\"" |
| }, |
| "passes": [ |
| { |
| "id": "main", |
| "name": "MeanVarianceNormalization.CooperativeVec4", |
| "source": { |
| "shader": "mean-variance-normalization-subgroup.wgsl.jinja", |
| "inputs": { |
| "xShape": "shapes.X", |
| "reduce": ["hasAxis(attrs.axes, 0, ranks.X)", "hasAxis(attrs.axes, 1, ranks.X)", "hasAxis(attrs.axes, 2, ranks.X)", "hasAxis(attrs.axes, 3, ranks.X)", "hasAxis(attrs.axes, 4, ranks.X)", "hasAxis(attrs.axes, 5, ranks.X)"], |
| "reduceCount": "reduceCount", |
| "wg": "vectorWorkgroupSize", |
| "minSubgroupSize": "minSubgroupSize", |
| "maxSubgroups": "ceilDiv(vectorWorkgroupSize, minSubgroupSize)", |
| "scalar": "dtypes.T", |
| "vecType": "\"vec4<\" ~ dtypes.T ~ \">\"", |
| "usesF16": "dtypes.T == \"f16\"", |
| "useSubgroups": "useSubgroups", |
| "vectorized": true |
| } |
| }, |
| "bindings": "rows", |
| "dispatch": { "workgroups": "rowCount" } |
| } |
| ] |
| }, |
| { |
| "id": "cooperative_scalar", |
| "priority": 100, |
| "when": ["shapeContract", "numel(shapes.X) > 0", "rowDispatchFits", "scalarStorageFits"], |
| "constants": { "scalar": "dtypes.T", "ioElement": "dtypes.T" }, |
| "passes": [ |
| { |
| "id": "main", |
| "name": "MeanVarianceNormalization.CooperativeScalar", |
| "source": { |
| "shader": "mean-variance-normalization-subgroup.wgsl.jinja", |
| "inputs": { |
| "xShape": "shapes.X", |
| "reduce": ["hasAxis(attrs.axes, 0, ranks.X)", "hasAxis(attrs.axes, 1, ranks.X)", "hasAxis(attrs.axes, 2, ranks.X)", "hasAxis(attrs.axes, 3, ranks.X)", "hasAxis(attrs.axes, 4, ranks.X)", "hasAxis(attrs.axes, 5, ranks.X)"], |
| "reduceCount": "reduceCount", |
| "wg": "scalarWorkgroupSize", |
| "minSubgroupSize": "minSubgroupSize", |
| "maxSubgroups": "ceilDiv(scalarWorkgroupSize, minSubgroupSize)", |
| "scalar": "dtypes.T", |
| "usesF16": "dtypes.T == \"f16\"", |
| "useSubgroups": "useSubgroups", |
| "vectorized": false |
| } |
| }, |
| "bindings": "rows", |
| "dispatch": { "workgroups": "rowCount" } |
| } |
| ] |
| } |
| ] |
| } |
|
|