sync 91d990483a17
Browse files- README.md +53 -13
- build/webgpu/bench.json +460 -8
- build/webgpu/conv-1x1-gemm-tiled-reg.wgsl.jinja +104 -42
- build/webgpu/conv-1x1-gemm-tiled.wgsl.jinja +28 -34
- build/webgpu/conv-1x1-subgroup-matrix.wgsl.jinja +302 -29
- build/webgpu/conv-direct-nd.wgsl.jinja +24 -19
- build/webgpu/conv-direct-unrolled.wgsl.jinja +36 -34
- build/webgpu/conv-im2col-nchw.wgsl.jinja +13 -14
- build/webgpu/conv-splitk-reduce.wgsl.jinja +83 -0
- build/webgpu/conv1d-tiled-reg.wgsl.jinja +29 -34
- build/webgpu/conv2d-grouped-large-w4.wgsl.jinja +128 -77
- build/webgpu/manifest.json +0 -0
- build/webgpu/metadata.json +80 -14
- build/webgpu/test.json +0 -0
README.md
CHANGED
|
@@ -18,18 +18,18 @@ See the [ONNX Runtime `FusedConv` contrib-operator spec](https://github.com/micr
|
|
| 18 |
|
| 19 |
## Inputs
|
| 20 |
|
| 21 |
-
| Name |
|
| 22 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
-
| `
|
| 24 |
-
| `
|
| 25 |
-
| `
|
| 26 |
-
| `
|
| 27 |
|
| 28 |
## Outputs
|
| 29 |
|
| 30 |
-
| Name |
|
| 31 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 32 |
-
| `
|
| 33 |
|
| 34 |
## Attributes
|
| 35 |
|
|
@@ -37,11 +37,11 @@ Attributes and default values (overridable per request):
|
|
| 37 |
|
| 38 |
| Attribute | Default | Description |
|
| 39 |
| --- | --- | --- |
|
| 40 |
-
| `auto_pad` | `"NOTSET"` | Automatic padding mode. `NOTSET` uses `pads`; `SAME_UPPER` and `SAME_LOWER` choose padding so each output spatial size is `ceil(input / stride)`; `VALID` uses no padding. |
|
| 41 |
-
| `group` | `1` | Number of groups that input and output channels are split into; defaults to 1. |
|
| 42 |
| `activation` | — | Optional fused activation name: `Relu`, `LeakyRelu`, `Sigmoid`, `Tanh`, `HardSigmoid`, `HardSwish`, or `Clip`. Omission applies no activation. |
|
| 43 |
| `activation_params` | — | Positional parameters for the fused activation: exactly `[alpha]` is required for `LeakyRelu`, and exactly `[alpha, beta]` or `[min, max]` is required for `HardSigmoid` or `Clip`, respectively. Parameter-free activations ignore this attribute. |
|
|
|
|
| 44 |
| `dilations` | — | Optional dilation factors, one positive integer per spatial axis. Omission means all ones. |
|
|
|
|
| 45 |
| `kernel_shape` | — | Optional kernel shape, one positive integer per spatial axis. When present, it must match the spatial dimensions of the weight tensor; omission infers the shape from the weights. |
|
| 46 |
| `pads` | — | Optional explicit padding in ONNX order `[begin_axis_0, ..., begin_axis_n, end_axis_0, ..., end_axis_n]`. Omission means all zeros; it cannot be combined with an automatic padding mode. |
|
| 47 |
| `strides` | — | Optional stride factors, one positive integer per spatial axis. Omission means all ones. |
|
|
@@ -52,13 +52,48 @@ Attributes and default values (overridable per request):
|
|
| 52 |
| --- | --- |
|
| 53 |
| `T` | `float32`, `float16` |
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
## Device requirements
|
| 56 |
|
| 57 |
-
Some implementation variants require `subgroup-matrix` and `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
|
| 58 |
|
| 59 |
## Files
|
| 60 |
|
| 61 |
-
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
|
| 62 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 63 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 64 |
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
|
@@ -68,15 +103,20 @@ Some implementation variants require `subgroup-matrix` and `subgroups`. These ar
|
|
| 68 |
- [`conv-direct-nd.wgsl.jinja`](build/webgpu/conv-direct-nd.wgsl.jinja)
|
| 69 |
- [`conv-direct-unrolled.wgsl.jinja`](build/webgpu/conv-direct-unrolled.wgsl.jinja)
|
| 70 |
- [`conv-im2col-nchw.wgsl.jinja`](build/webgpu/conv-im2col-nchw.wgsl.jinja)
|
|
|
|
| 71 |
- [`conv1d-tiled-reg.wgsl.jinja`](build/webgpu/conv1d-tiled-reg.wgsl.jinja)
|
| 72 |
- [`conv2d-grouped-large-w4.wgsl.jinja`](build/webgpu/conv2d-grouped-large-w4.wgsl.jinja)
|
| 73 |
|
| 74 |
## Use with `@huggingface/kernels`
|
| 75 |
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
|
|
|
| 80 |
|
| 81 |
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 82 |
|
|
|
|
| 18 |
|
| 19 |
## Inputs
|
| 20 |
|
| 21 |
+
| Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
|
| 22 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `x` | `X` | `T` | — | — | Input data tensor of shape `(N, C, D1, ..., Dn)` for one to three spatial dimensions. | required |
|
| 24 |
+
| `w` | `W` | `T` | — | — | Convolution filter tensor of shape `(M, C/group, k1, ..., kn)`, with the same spatial rank as `X`. | required |
|
| 25 |
+
| `bias` | `B` | `T` | `1` | — | Optional 1-D bias tensor of length `out_channels`, broadcast-added to each output channel. | optional |
|
| 26 |
+
| `zResidual` | `Z` | `T` | same as `x` | — | Optional residual tensor with the same shape as the output `Y`, added before the activation. | optional |
|
| 27 |
|
| 28 |
## Outputs
|
| 29 |
|
| 30 |
+
| Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
|
| 31 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 32 |
+
| `y` | `Y` | `T` | same as `x` | derived | Output feature map tensor after convolution, optional bias/residual addition, and the fused activation. | required |
|
| 33 |
|
| 34 |
## Attributes
|
| 35 |
|
|
|
|
| 37 |
|
| 38 |
| Attribute | Default | Description |
|
| 39 |
| --- | --- | --- |
|
|
|
|
|
|
|
| 40 |
| `activation` | — | Optional fused activation name: `Relu`, `LeakyRelu`, `Sigmoid`, `Tanh`, `HardSigmoid`, `HardSwish`, or `Clip`. Omission applies no activation. |
|
| 41 |
| `activation_params` | — | Positional parameters for the fused activation: exactly `[alpha]` is required for `LeakyRelu`, and exactly `[alpha, beta]` or `[min, max]` is required for `HardSigmoid` or `Clip`, respectively. Parameter-free activations ignore this attribute. |
|
| 42 |
+
| `auto_pad` | `"NOTSET"` | Automatic padding mode. `NOTSET` uses `pads`; `SAME_UPPER` and `SAME_LOWER` choose padding so each output spatial size is `ceil(input / stride)`; `VALID` uses no padding. |
|
| 43 |
| `dilations` | — | Optional dilation factors, one positive integer per spatial axis. Omission means all ones. |
|
| 44 |
+
| `group` | `1` | Number of groups that input and output channels are split into; defaults to 1. |
|
| 45 |
| `kernel_shape` | — | Optional kernel shape, one positive integer per spatial axis. When present, it must match the spatial dimensions of the weight tensor; omission infers the shape from the weights. |
|
| 46 |
| `pads` | — | Optional explicit padding in ONNX order `[begin_axis_0, ..., begin_axis_n, end_axis_0, ..., end_axis_n]`. Omission means all zeros; it cannot be combined with an automatic padding mode. |
|
| 47 |
| `strides` | — | Optional stride factors, one positive integer per spatial axis. Omission means all ones. |
|
|
|
|
| 52 |
| --- | --- |
|
| 53 |
| `T` | `float32`, `float16` |
|
| 54 |
|
| 55 |
+
## Implementation variants
|
| 56 |
+
|
| 57 |
+
One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
|
| 58 |
+
|
| 59 |
+
- `implicit_im2col_tiled_reg_splitk` — Partitions the register-blocked implicit-im2col reduction across workgroups when the unsplit output grid is too small, then combines raw partial sums and applies the bias and fused activation once. The combine reassociates floating-point addition relative to the unsplit route.
|
| 60 |
+
- `implicit_im2col_tiled_bias_reg_splitk` — Partitions the register-blocked implicit-im2col reduction across workgroups when the unsplit output grid is too small, then combines raw partial sums and applies the bias and fused activation once. The combine reassociates floating-point addition relative to the unsplit route.
|
| 61 |
+
- `implicit_im2col_tiled_reg_splitk_preferred` — Partitions the register-blocked implicit-im2col reduction across workgroups when the unsplit output grid is too small, then combines raw partial sums and applies the bias and fused activation once. The combine reassociates floating-point addition relative to the unsplit route.
|
| 62 |
+
- `implicit_im2col_tiled_bias_reg_splitk_preferred` — Partitions the register-blocked implicit-im2col reduction across workgroups when the unsplit output grid is too small, then combines raw partial sums and applies the bias and fused activation once. The combine reassociates floating-point addition relative to the unsplit route.
|
| 63 |
+
- `implicit_im2col_tiled_reg` — Gathers logical im2col elements directly from `X` into register-blocked GEMM tiles and applies the fused activation in the epilogue, avoiding a materialized column scratch buffer.
|
| 64 |
+
- `implicit_im2col_tiled_bias_reg` — Gathers logical im2col elements directly from `X` into register-blocked GEMM tiles and applies bias and the fused activation in the epilogue, avoiding a materialized column scratch buffer.
|
| 65 |
+
- `gemm_1x1_subgroup_matrix` — Multiplies 1x1 weights directly by NCHW input, then applies the selected bias, residual and activation epilogue. Complete output-channel tiles bypass input staging and reduction-loop barriers; partial tiles retain guarded staging. Tile geometry follows the device's workgroup limits.
|
| 66 |
+
- `gemm_1x1_subgroup_matrix_z` — Multiplies 1x1 weights directly by NCHW input, then applies the selected bias, residual and activation epilogue. Complete output-channel tiles bypass input staging and reduction-loop barriers; partial tiles retain guarded staging. Tile geometry follows the device's workgroup limits.
|
| 67 |
+
- `gemm_1x1_subgroup_matrix_bias` — Multiplies 1x1 weights directly by NCHW input, then applies the selected bias, residual and activation epilogue. Complete output-channel tiles bypass input staging and reduction-loop barriers; partial tiles retain guarded staging. Tile geometry follows the device's workgroup limits.
|
| 68 |
+
- `gemm_1x1_subgroup_matrix_bias_z` — Multiplies 1x1 weights directly by NCHW input, then applies the selected bias, residual and activation epilogue. Complete output-channel tiles bypass input staging and reduction-loop barriers; partial tiles retain guarded staging. Tile geometry follows the device's workgroup limits.
|
| 69 |
+
- `im2col_gemm_subgroup_matrix` — Multiply aligned materialized columns through workgroup-staged subgroup matrices with f32 accumulation and the fused epilogue.
|
| 70 |
+
- `im2col_direct_inputs_subgroup_matrix` — Multiply aligned materialized f32 columns through direct subgroup-matrix loads, retaining the f32 accumulation order and fused epilogue while omitting operand staging and K-loop barriers.
|
| 71 |
+
- `im2col_gemm_subgroup_matrix_z` — Multiply aligned materialized columns through workgroup-staged subgroup matrices with f32 accumulation and the fused epilogue.
|
| 72 |
+
- `im2col_direct_inputs_subgroup_matrix_z` — Multiply aligned materialized f32 columns through direct subgroup-matrix loads, retaining the f32 accumulation order and fused epilogue while omitting operand staging and K-loop barriers.
|
| 73 |
+
- `im2col_gemm_subgroup_matrix_bias` — Multiply aligned materialized columns through workgroup-staged subgroup matrices with f32 accumulation and the fused epilogue.
|
| 74 |
+
- `im2col_direct_inputs_subgroup_matrix_bias` — Multiply aligned materialized f32 columns through direct subgroup-matrix loads, retaining the f32 accumulation order and fused epilogue while omitting operand staging and K-loop barriers.
|
| 75 |
+
- `im2col_gemm_subgroup_matrix_bias_z` — Multiply aligned materialized columns through workgroup-staged subgroup matrices with f32 accumulation and the fused epilogue.
|
| 76 |
+
- `im2col_direct_inputs_subgroup_matrix_bias_z` — Multiply aligned materialized f32 columns through direct subgroup-matrix loads, retaining the f32 accumulation order and fused epilogue while omitting operand staging and K-loop barriers.
|
| 77 |
+
- `im2col_half_direct_subgroup_matrix` — Materialize aligned f16 convolution columns without widening their storage, then load weights and columns directly into subgroup matrices. Preserve f32 accumulation and the existing bias, residual, activation and f16 output rounding while removing operand staging and K-loop barriers.
|
| 78 |
+
- `im2col_half_direct_subgroup_matrix_z` — Materialize aligned f16 convolution columns without widening their storage, then load weights and columns directly into subgroup matrices. Preserve f32 accumulation and the existing bias, residual, activation and f16 output rounding while removing operand staging and K-loop barriers.
|
| 79 |
+
- `im2col_half_direct_subgroup_matrix_bias` — Materialize aligned f16 convolution columns without widening their storage, then load weights and columns directly into subgroup matrices. Preserve f32 accumulation and the existing bias, residual, activation and f16 output rounding while removing operand staging and K-loop barriers.
|
| 80 |
+
- `im2col_half_direct_subgroup_matrix_bias_z` — Materialize aligned f16 convolution columns without widening their storage, then load weights and columns directly into subgroup matrices. Preserve f32 accumulation and the existing bias, residual, activation and f16 output rounding while removing operand staging and K-loop barriers.
|
| 81 |
+
- `implicit_im2col_subgroup_matrix` — Gathers convolution input tiles directly into subgroup-matrix operands and applies the existing bias, residual, and activation epilogue, avoiding a materialized column matrix when output-channel reuse permits or both column layouts exceed device allocation limits.
|
| 82 |
+
- `implicit_im2col_subgroup_matrix_z` — Gathers convolution input tiles directly into subgroup-matrix operands and applies the existing bias, residual, and activation epilogue, avoiding a materialized column matrix when output-channel reuse permits or both column layouts exceed device allocation limits.
|
| 83 |
+
- `implicit_im2col_subgroup_matrix_bias` — Gathers convolution input tiles directly into subgroup-matrix operands and applies the existing bias, residual, and activation epilogue, avoiding a materialized column matrix when output-channel reuse permits or both column layouts exceed device allocation limits.
|
| 84 |
+
- `implicit_im2col_subgroup_matrix_bias_z` — Gathers convolution input tiles directly into subgroup-matrix operands and applies the existing bias, residual, and activation epilogue, avoiding a materialized column matrix when output-channel reuse permits or both column layouts exceed device allocation limits.
|
| 85 |
+
- `implicit_im2col_subgroup_matrix_f16` — Gathers convolution input tiles directly into subgroup-matrix operands and applies the existing bias, residual, and activation epilogue, avoiding a materialized column matrix when output-channel reuse permits or both column layouts exceed device allocation limits.
|
| 86 |
+
- `implicit_im2col_subgroup_matrix_z_f16` — Gathers convolution input tiles directly into subgroup-matrix operands and applies the existing bias, residual, and activation epilogue, avoiding a materialized column matrix when output-channel reuse permits or both column layouts exceed device allocation limits.
|
| 87 |
+
- `implicit_im2col_subgroup_matrix_bias_f16` — Gathers convolution input tiles directly into subgroup-matrix operands and applies the existing bias, residual, and activation epilogue, avoiding a materialized column matrix when output-channel reuse permits or both column layouts exceed device allocation limits.
|
| 88 |
+
- `implicit_im2col_subgroup_matrix_bias_z_f16` — Gathers convolution input tiles directly into subgroup-matrix operands and applies the existing bias, residual, and activation epilogue, avoiding a materialized column matrix when output-channel reuse permits or both column layouts exceed device allocation limits.
|
| 89 |
+
|
| 90 |
## Device requirements
|
| 91 |
|
| 92 |
+
Some implementation variants require `subgroup-matrix`, `shader-f16`, and `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
|
| 93 |
|
| 94 |
## Files
|
| 95 |
|
| 96 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
|
| 97 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 98 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 99 |
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
|
|
|
| 103 |
- [`conv-direct-nd.wgsl.jinja`](build/webgpu/conv-direct-nd.wgsl.jinja)
|
| 104 |
- [`conv-direct-unrolled.wgsl.jinja`](build/webgpu/conv-direct-unrolled.wgsl.jinja)
|
| 105 |
- [`conv-im2col-nchw.wgsl.jinja`](build/webgpu/conv-im2col-nchw.wgsl.jinja)
|
| 106 |
+
- [`conv-splitk-reduce.wgsl.jinja`](build/webgpu/conv-splitk-reduce.wgsl.jinja)
|
| 107 |
- [`conv1d-tiled-reg.wgsl.jinja`](build/webgpu/conv1d-tiled-reg.wgsl.jinja)
|
| 108 |
- [`conv2d-grouped-large-w4.wgsl.jinja`](build/webgpu/conv2d-grouped-large-w4.wgsl.jinja)
|
| 109 |
|
| 110 |
## Use with `@huggingface/kernels`
|
| 111 |
|
| 112 |
+
```sh
|
| 113 |
+
npm install --save-exact @huggingface/kernels@0.0.1-preview.2
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
|
| 117 |
|
| 118 |
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 119 |
+
It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
|
| 120 |
|
| 121 |
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 122 |
|
build/webgpu/bench.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
{
|
| 2 |
-
"op": "com.microsoft.FusedConv",
|
| 3 |
"tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
|
| 4 |
"cases": [
|
| 5 |
{
|
|
@@ -227,8 +226,8 @@
|
|
| 227 |
"name": "fusedconv-f32-1x1-bias-b1c256m256-32x32",
|
| 228 |
"preset": "smoke",
|
| 229 |
"provenance": {
|
| 230 |
-
"source": "
|
| 231 |
-
"notes": "
|
| 232 |
},
|
| 233 |
"vars": {
|
| 234 |
"batch": 1,
|
|
@@ -667,8 +666,8 @@
|
|
| 667 |
"name": "fusedconv-3x3-dense-bias-c256m256-32x32",
|
| 668 |
"preset": "smoke",
|
| 669 |
"provenance": {
|
| 670 |
-
"source": "
|
| 671 |
-
"notes": "
|
| 672 |
},
|
| 673 |
"vars": {
|
| 674 |
"batch": 1,
|
|
@@ -826,7 +825,7 @@
|
|
| 826 |
{
|
| 827 |
"name": "fusedconv-group2-k7-c64m64-64x64-direct-fallback",
|
| 828 |
"provenance": {
|
| 829 |
-
"notes": "
|
| 830 |
},
|
| 831 |
"preset": "stress",
|
| 832 |
"vars": {
|
|
@@ -861,7 +860,7 @@
|
|
| 861 |
{
|
| 862 |
"name": "fusedconv-group4-k5-c128m128-48x48-direct-fallback",
|
| 863 |
"provenance": {
|
| 864 |
-
"notes": "
|
| 865 |
},
|
| 866 |
"preset": "stress",
|
| 867 |
"vars": {
|
|
@@ -898,7 +897,7 @@
|
|
| 898 |
"preset": "stress",
|
| 899 |
"provenance": {
|
| 900 |
"source": "ONNX Runtime FusedConv provider semantics",
|
| 901 |
-
"notes": "
|
| 902 |
},
|
| 903 |
"vars": { "batch": 1, "inChannels": 80, "outChannels": 512, "inW": 3000, "kernelW": 3, "strideW": 1, "padW": 1 },
|
| 904 |
"attrs": { "activation": "HardSwish", "strides": [1], "pads": [1, 1] },
|
|
@@ -917,6 +916,459 @@
|
|
| 917 |
}
|
| 918 |
]
|
| 919 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 920 |
}
|
| 921 |
]
|
| 922 |
}
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
|
| 3 |
"cases": [
|
| 4 |
{
|
|
|
|
| 226 |
"name": "fusedconv-f32-1x1-bias-b1c256m256-32x32",
|
| 227 |
"preset": "smoke",
|
| 228 |
"provenance": {
|
| 229 |
+
"source": "synthetic",
|
| 230 |
+
"notes": "A biased 1x1 convolution produces 64 register tiles, measuring the register-tiled route at its admission floor."
|
| 231 |
},
|
| 232 |
"vars": {
|
| 233 |
"batch": 1,
|
|
|
|
| 666 |
"name": "fusedconv-3x3-dense-bias-c256m256-32x32",
|
| 667 |
"preset": "smoke",
|
| 668 |
"provenance": {
|
| 669 |
+
"source": "synthetic",
|
| 670 |
+
"notes": "A biased 3x3 convolution produces 64 register tiles, measuring the register-tiled im2col route at its admission floor."
|
| 671 |
},
|
| 672 |
"vars": {
|
| 673 |
"batch": 1,
|
|
|
|
| 825 |
{
|
| 826 |
"name": "fusedconv-group2-k7-c64m64-64x64-direct-fallback",
|
| 827 |
"provenance": {
|
| 828 |
+
"notes": "A group-2, 7x7 convolution with wide output exercises the four-wide grouped large-kernel route."
|
| 829 |
},
|
| 830 |
"preset": "stress",
|
| 831 |
"vars": {
|
|
|
|
| 860 |
{
|
| 861 |
"name": "fusedconv-group4-k5-c128m128-48x48-direct-fallback",
|
| 862 |
"provenance": {
|
| 863 |
+
"notes": "A group-4, 5x5 convolution exercises the four-wide grouped large-kernel route near its kernel-size boundary."
|
| 864 |
},
|
| 865 |
"preset": "stress",
|
| 866 |
"vars": {
|
|
|
|
| 897 |
"preset": "stress",
|
| 898 |
"provenance": {
|
| 899 |
"source": "ONNX Runtime FusedConv provider semantics",
|
| 900 |
+
"notes": "A large aligned temporal convolution with bias and fused HardSwish exercises the register-tiled implicit-GEMM path."
|
| 901 |
},
|
| 902 |
"vars": { "batch": 1, "inChannels": 80, "outChannels": 512, "inW": 3000, "kernelW": 3, "strideW": 1, "padW": 1 },
|
| 903 |
"attrs": { "activation": "HardSwish", "strides": [1], "pads": [1, 1] },
|
|
|
|
| 916 |
}
|
| 917 |
]
|
| 918 |
}
|
| 919 |
+
},
|
| 920 |
+
{
|
| 921 |
+
"name": "fusedconv-splitk-grid32-b1m128-32x32",
|
| 922 |
+
"preset": "smoke",
|
| 923 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 924 |
+
"inputs": {
|
| 925 |
+
"x": { "shape": [1, 64, 32, 32], "dtype": "float32", "dist": "normal", "seed": 7770, "scale": 0.2 },
|
| 926 |
+
"w": { "shape": [128, 64, 3, 3], "dtype": "float32", "dist": "normal", "seed": 2676, "scale": 0.05 }
|
| 927 |
+
},
|
| 928 |
+
"outputs": { "y": { "shape": [1, 128, 32, 32], "dtype": "float32" } },
|
| 929 |
+
"bench": {
|
| 930 |
+
"metrics": [
|
| 931 |
+
{ "type": "gflops", "value": "2 * numel(shapes.y) * dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)" }
|
| 932 |
+
]
|
| 933 |
+
},
|
| 934 |
+
"provenance": {
|
| 935 |
+
"source": "synthetic",
|
| 936 |
+
"notes": "Measures the 32-workgroup implicit split-K preference boundary with output channels, spatial extent, or batch contributing to the grid."
|
| 937 |
+
}
|
| 938 |
+
},
|
| 939 |
+
{
|
| 940 |
+
"name": "fusedconv-splitk-grid32-b1m64-32x64",
|
| 941 |
+
"preset": "smoke",
|
| 942 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 943 |
+
"inputs": {
|
| 944 |
+
"x": { "shape": [1, 64, 32, 64], "dtype": "float32", "dist": "normal", "seed": 7770, "scale": 0.2 },
|
| 945 |
+
"w": { "shape": [64, 64, 3, 3], "dtype": "float32", "dist": "normal", "seed": 2676, "scale": 0.05 }
|
| 946 |
+
},
|
| 947 |
+
"outputs": { "y": { "shape": [1, 64, 32, 64], "dtype": "float32" } },
|
| 948 |
+
"bench": {
|
| 949 |
+
"metrics": [
|
| 950 |
+
{ "type": "gflops", "value": "2 * numel(shapes.y) * dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)" }
|
| 951 |
+
]
|
| 952 |
+
},
|
| 953 |
+
"provenance": {
|
| 954 |
+
"source": "synthetic",
|
| 955 |
+
"notes": "Measures the 32-workgroup implicit split-K preference boundary with output channels, spatial extent, or batch contributing to the grid."
|
| 956 |
+
}
|
| 957 |
+
},
|
| 958 |
+
{
|
| 959 |
+
"name": "fusedconv-splitk-grid32-b2m64-32x32",
|
| 960 |
+
"preset": "smoke",
|
| 961 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 962 |
+
"inputs": {
|
| 963 |
+
"x": { "shape": [2, 64, 32, 32], "dtype": "float32", "dist": "normal", "seed": 7770, "scale": 0.2 },
|
| 964 |
+
"w": { "shape": [64, 64, 3, 3], "dtype": "float32", "dist": "normal", "seed": 2676, "scale": 0.05 }
|
| 965 |
+
},
|
| 966 |
+
"outputs": { "y": { "shape": [2, 64, 32, 32], "dtype": "float32" } },
|
| 967 |
+
"bench": {
|
| 968 |
+
"metrics": [
|
| 969 |
+
{ "type": "gflops", "value": "2 * numel(shapes.y) * dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)" }
|
| 970 |
+
]
|
| 971 |
+
},
|
| 972 |
+
"provenance": {
|
| 973 |
+
"source": "synthetic",
|
| 974 |
+
"notes": "Measures the 32-workgroup implicit split-K preference boundary with output channels, spatial extent, or batch contributing to the grid."
|
| 975 |
+
}
|
| 976 |
+
},
|
| 977 |
+
{
|
| 978 |
+
"name": "fusedconv-direct-inputs-row-band-m32-k288-n256",
|
| 979 |
+
"preset": "smoke",
|
| 980 |
+
"vars": {
|
| 981 |
+
"batch": 1,
|
| 982 |
+
"inChannels": 32,
|
| 983 |
+
"outChannels": 32,
|
| 984 |
+
"inH": 16,
|
| 985 |
+
"inW": 16,
|
| 986 |
+
"kernelH": 3,
|
| 987 |
+
"kernelW": 3,
|
| 988 |
+
"strideH": 1,
|
| 989 |
+
"strideW": 1,
|
| 990 |
+
"padH": 1,
|
| 991 |
+
"padW": 1
|
| 992 |
+
},
|
| 993 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 994 |
+
"inputs": {
|
| 995 |
+
"x": { "shape": [1, 32, 16, 16], "dtype": "float32", "dist": "normal", "seed": 839, "scale": 0.2 },
|
| 996 |
+
"w": { "shape": [32, 32, 3, 3], "dtype": "float32", "dist": "normal", "seed": 1375, "scale": 0.05 }
|
| 997 |
+
},
|
| 998 |
+
"outputs": { "y": { "shape": [1, 32, 16, 16], "dtype": "float32" } },
|
| 999 |
+
"bench": {
|
| 1000 |
+
"metrics": [
|
| 1001 |
+
{
|
| 1002 |
+
"type": "gflops",
|
| 1003 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 1004 |
+
}
|
| 1005 |
+
]
|
| 1006 |
+
}
|
| 1007 |
+
},
|
| 1008 |
+
{
|
| 1009 |
+
"name": "fusedconv-direct-inputs-row-band-m64-k288-n256",
|
| 1010 |
+
"preset": "smoke",
|
| 1011 |
+
"vars": {
|
| 1012 |
+
"batch": 1,
|
| 1013 |
+
"inChannels": 32,
|
| 1014 |
+
"outChannels": 64,
|
| 1015 |
+
"inH": 16,
|
| 1016 |
+
"inW": 16,
|
| 1017 |
+
"kernelH": 3,
|
| 1018 |
+
"kernelW": 3,
|
| 1019 |
+
"strideH": 1,
|
| 1020 |
+
"strideW": 1,
|
| 1021 |
+
"padH": 1,
|
| 1022 |
+
"padW": 1
|
| 1023 |
+
},
|
| 1024 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 1025 |
+
"inputs": {
|
| 1026 |
+
"x": { "shape": [1, 32, 16, 16], "dtype": "float32", "dist": "normal", "seed": 839, "scale": 0.2 },
|
| 1027 |
+
"w": { "shape": [64, 32, 3, 3], "dtype": "float32", "dist": "normal", "seed": 1375, "scale": 0.05 }
|
| 1028 |
+
},
|
| 1029 |
+
"outputs": { "y": { "shape": [1, 64, 16, 16], "dtype": "float32" } },
|
| 1030 |
+
"bench": {
|
| 1031 |
+
"metrics": [
|
| 1032 |
+
{
|
| 1033 |
+
"type": "gflops",
|
| 1034 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 1035 |
+
}
|
| 1036 |
+
]
|
| 1037 |
+
}
|
| 1038 |
+
},
|
| 1039 |
+
{
|
| 1040 |
+
"name": "fusedconv-half-direct-inputs-row-band-m32-k288-n256",
|
| 1041 |
+
"preset": "smoke",
|
| 1042 |
+
"vars": {
|
| 1043 |
+
"batch": 1,
|
| 1044 |
+
"inChannels": 32,
|
| 1045 |
+
"outChannels": 32,
|
| 1046 |
+
"inH": 16,
|
| 1047 |
+
"inW": 16,
|
| 1048 |
+
"kernelH": 3,
|
| 1049 |
+
"kernelW": 3,
|
| 1050 |
+
"strideH": 1,
|
| 1051 |
+
"strideW": 1,
|
| 1052 |
+
"padH": 1,
|
| 1053 |
+
"padW": 1
|
| 1054 |
+
},
|
| 1055 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 1056 |
+
"inputs": {
|
| 1057 |
+
"x": { "shape": [1, 32, 16, 16], "dtype": "float16", "dist": "normal", "seed": 839, "scale": 0.2 },
|
| 1058 |
+
"w": { "shape": [32, 32, 3, 3], "dtype": "float16", "dist": "normal", "seed": 1375, "scale": 0.05 }
|
| 1059 |
+
},
|
| 1060 |
+
"outputs": { "y": { "shape": [1, 32, 16, 16], "dtype": "float16" } },
|
| 1061 |
+
"bench": {
|
| 1062 |
+
"metrics": [
|
| 1063 |
+
{
|
| 1064 |
+
"type": "gflops",
|
| 1065 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 1066 |
+
}
|
| 1067 |
+
]
|
| 1068 |
+
}
|
| 1069 |
+
},
|
| 1070 |
+
{
|
| 1071 |
+
"name": "fusedconv-half-direct-inputs-row-band-m64-k288-n256",
|
| 1072 |
+
"preset": "smoke",
|
| 1073 |
+
"vars": {
|
| 1074 |
+
"batch": 1,
|
| 1075 |
+
"inChannels": 32,
|
| 1076 |
+
"outChannels": 64,
|
| 1077 |
+
"inH": 16,
|
| 1078 |
+
"inW": 16,
|
| 1079 |
+
"kernelH": 3,
|
| 1080 |
+
"kernelW": 3,
|
| 1081 |
+
"strideH": 1,
|
| 1082 |
+
"strideW": 1,
|
| 1083 |
+
"padH": 1,
|
| 1084 |
+
"padW": 1
|
| 1085 |
+
},
|
| 1086 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 1087 |
+
"inputs": {
|
| 1088 |
+
"x": { "shape": [1, 32, 16, 16], "dtype": "float16", "dist": "normal", "seed": 839, "scale": 0.2 },
|
| 1089 |
+
"w": { "shape": [64, 32, 3, 3], "dtype": "float16", "dist": "normal", "seed": 1375, "scale": 0.05 }
|
| 1090 |
+
},
|
| 1091 |
+
"outputs": { "y": { "shape": [1, 64, 16, 16], "dtype": "float16" } },
|
| 1092 |
+
"bench": {
|
| 1093 |
+
"metrics": [
|
| 1094 |
+
{
|
| 1095 |
+
"type": "gflops",
|
| 1096 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 1097 |
+
}
|
| 1098 |
+
]
|
| 1099 |
+
}
|
| 1100 |
+
},
|
| 1101 |
+
{
|
| 1102 |
+
"name": "fusedconv-half-direct-inputs-row-band-m96-k288-n256",
|
| 1103 |
+
"preset": "smoke",
|
| 1104 |
+
"vars": {
|
| 1105 |
+
"batch": 1,
|
| 1106 |
+
"inChannels": 32,
|
| 1107 |
+
"outChannels": 96,
|
| 1108 |
+
"inH": 16,
|
| 1109 |
+
"inW": 16,
|
| 1110 |
+
"kernelH": 3,
|
| 1111 |
+
"kernelW": 3,
|
| 1112 |
+
"strideH": 1,
|
| 1113 |
+
"strideW": 1,
|
| 1114 |
+
"padH": 1,
|
| 1115 |
+
"padW": 1
|
| 1116 |
+
},
|
| 1117 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 1118 |
+
"inputs": {
|
| 1119 |
+
"x": { "shape": [1, 32, 16, 16], "dtype": "float16", "dist": "normal", "seed": 839, "scale": 0.2 },
|
| 1120 |
+
"w": { "shape": [96, 32, 3, 3], "dtype": "float16", "dist": "normal", "seed": 1375, "scale": 0.05 }
|
| 1121 |
+
},
|
| 1122 |
+
"outputs": { "y": { "shape": [1, 96, 16, 16], "dtype": "float16" } },
|
| 1123 |
+
"bench": {
|
| 1124 |
+
"metrics": [
|
| 1125 |
+
{
|
| 1126 |
+
"type": "gflops",
|
| 1127 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 1128 |
+
}
|
| 1129 |
+
]
|
| 1130 |
+
}
|
| 1131 |
+
},
|
| 1132 |
+
{
|
| 1133 |
+
"name": "fusedconv-half-direct-inputs-row-band-m128-k288-n256",
|
| 1134 |
+
"preset": "smoke",
|
| 1135 |
+
"vars": {
|
| 1136 |
+
"batch": 1,
|
| 1137 |
+
"inChannels": 32,
|
| 1138 |
+
"outChannels": 128,
|
| 1139 |
+
"inH": 16,
|
| 1140 |
+
"inW": 16,
|
| 1141 |
+
"kernelH": 3,
|
| 1142 |
+
"kernelW": 3,
|
| 1143 |
+
"strideH": 1,
|
| 1144 |
+
"strideW": 1,
|
| 1145 |
+
"padH": 1,
|
| 1146 |
+
"padW": 1
|
| 1147 |
+
},
|
| 1148 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 1149 |
+
"inputs": {
|
| 1150 |
+
"x": { "shape": [1, 32, 16, 16], "dtype": "float16", "dist": "normal", "seed": 839, "scale": 0.2 },
|
| 1151 |
+
"w": { "shape": [128, 32, 3, 3], "dtype": "float16", "dist": "normal", "seed": 1375, "scale": 0.05 }
|
| 1152 |
+
},
|
| 1153 |
+
"outputs": { "y": { "shape": [1, 128, 16, 16], "dtype": "float16" } },
|
| 1154 |
+
"bench": {
|
| 1155 |
+
"metrics": [
|
| 1156 |
+
{
|
| 1157 |
+
"type": "gflops",
|
| 1158 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 1159 |
+
}
|
| 1160 |
+
]
|
| 1161 |
+
}
|
| 1162 |
+
},
|
| 1163 |
+
{
|
| 1164 |
+
"name": "direct-1x1-float32-m63-k512-n4096",
|
| 1165 |
+
"preset": "model",
|
| 1166 |
+
"vars": {
|
| 1167 |
+
"batch": 1,
|
| 1168 |
+
"inChannels": 512,
|
| 1169 |
+
"outChannels": 63,
|
| 1170 |
+
"inH": 64,
|
| 1171 |
+
"inW": 64,
|
| 1172 |
+
"kernelH": 1,
|
| 1173 |
+
"kernelW": 1,
|
| 1174 |
+
"strideH": 1,
|
| 1175 |
+
"strideW": 1,
|
| 1176 |
+
"padH": 0,
|
| 1177 |
+
"padW": 0
|
| 1178 |
+
},
|
| 1179 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 1180 |
+
"inputs": {
|
| 1181 |
+
"x": { "shape": [1, 512, 64, 64], "dtype": "float32", "dist": "normal", "seed": 354, "scale": 0.2 },
|
| 1182 |
+
"w": { "shape": [63, 512, 1, 1], "dtype": "float32", "dist": "normal", "seed": 355, "scale": 0.02 }
|
| 1183 |
+
},
|
| 1184 |
+
"outputs": { "y": { "shape": [1, 63, 64, 64], "dtype": "float32" } },
|
| 1185 |
+
"bench": {
|
| 1186 |
+
"metrics": [
|
| 1187 |
+
{
|
| 1188 |
+
"type": "gflops",
|
| 1189 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 1190 |
+
}
|
| 1191 |
+
]
|
| 1192 |
+
},
|
| 1193 |
+
"provenance": {
|
| 1194 |
+
"source": "synthetic",
|
| 1195 |
+
"notes": "Row-tile boundary sweep: full, partial, and device-dependent complete row bands share the same reduction and spatial extent."
|
| 1196 |
+
}
|
| 1197 |
+
},
|
| 1198 |
+
{
|
| 1199 |
+
"name": "direct-1x1-float32-m64-k512-n4096",
|
| 1200 |
+
"preset": "model",
|
| 1201 |
+
"vars": {
|
| 1202 |
+
"batch": 1,
|
| 1203 |
+
"inChannels": 512,
|
| 1204 |
+
"outChannels": 64,
|
| 1205 |
+
"inH": 64,
|
| 1206 |
+
"inW": 64,
|
| 1207 |
+
"kernelH": 1,
|
| 1208 |
+
"kernelW": 1,
|
| 1209 |
+
"strideH": 1,
|
| 1210 |
+
"strideW": 1,
|
| 1211 |
+
"padH": 0,
|
| 1212 |
+
"padW": 0
|
| 1213 |
+
},
|
| 1214 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 1215 |
+
"inputs": {
|
| 1216 |
+
"x": { "shape": [1, 512, 64, 64], "dtype": "float32", "dist": "normal", "seed": 354, "scale": 0.2 },
|
| 1217 |
+
"w": { "shape": [64, 512, 1, 1], "dtype": "float32", "dist": "normal", "seed": 355, "scale": 0.02 }
|
| 1218 |
+
},
|
| 1219 |
+
"outputs": { "y": { "shape": [1, 64, 64, 64], "dtype": "float32" } },
|
| 1220 |
+
"bench": {
|
| 1221 |
+
"metrics": [
|
| 1222 |
+
{
|
| 1223 |
+
"type": "gflops",
|
| 1224 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 1225 |
+
}
|
| 1226 |
+
]
|
| 1227 |
+
},
|
| 1228 |
+
"provenance": {
|
| 1229 |
+
"source": "synthetic",
|
| 1230 |
+
"notes": "Row-tile boundary sweep: full, partial, and device-dependent complete row bands share the same reduction and spatial extent."
|
| 1231 |
+
}
|
| 1232 |
+
},
|
| 1233 |
+
{
|
| 1234 |
+
"name": "direct-1x1-float32-m96-k512-n4096",
|
| 1235 |
+
"preset": "model",
|
| 1236 |
+
"vars": {
|
| 1237 |
+
"batch": 1,
|
| 1238 |
+
"inChannels": 512,
|
| 1239 |
+
"outChannels": 96,
|
| 1240 |
+
"inH": 64,
|
| 1241 |
+
"inW": 64,
|
| 1242 |
+
"kernelH": 1,
|
| 1243 |
+
"kernelW": 1,
|
| 1244 |
+
"strideH": 1,
|
| 1245 |
+
"strideW": 1,
|
| 1246 |
+
"padH": 0,
|
| 1247 |
+
"padW": 0
|
| 1248 |
+
},
|
| 1249 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 1250 |
+
"inputs": {
|
| 1251 |
+
"x": { "shape": [1, 512, 64, 64], "dtype": "float32", "dist": "normal", "seed": 354, "scale": 0.2 },
|
| 1252 |
+
"w": { "shape": [96, 512, 1, 1], "dtype": "float32", "dist": "normal", "seed": 355, "scale": 0.02 }
|
| 1253 |
+
},
|
| 1254 |
+
"outputs": { "y": { "shape": [1, 96, 64, 64], "dtype": "float32" } },
|
| 1255 |
+
"bench": {
|
| 1256 |
+
"metrics": [
|
| 1257 |
+
{
|
| 1258 |
+
"type": "gflops",
|
| 1259 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 1260 |
+
}
|
| 1261 |
+
]
|
| 1262 |
+
},
|
| 1263 |
+
"provenance": {
|
| 1264 |
+
"source": "synthetic",
|
| 1265 |
+
"notes": "Row-tile boundary sweep: full, partial, and device-dependent complete row bands share the same reduction and spatial extent."
|
| 1266 |
+
}
|
| 1267 |
+
},
|
| 1268 |
+
{
|
| 1269 |
+
"name": "direct-1x1-float16-m63-k512-n4096",
|
| 1270 |
+
"preset": "model",
|
| 1271 |
+
"vars": {
|
| 1272 |
+
"batch": 1,
|
| 1273 |
+
"inChannels": 512,
|
| 1274 |
+
"outChannels": 63,
|
| 1275 |
+
"inH": 64,
|
| 1276 |
+
"inW": 64,
|
| 1277 |
+
"kernelH": 1,
|
| 1278 |
+
"kernelW": 1,
|
| 1279 |
+
"strideH": 1,
|
| 1280 |
+
"strideW": 1,
|
| 1281 |
+
"padH": 0,
|
| 1282 |
+
"padW": 0
|
| 1283 |
+
},
|
| 1284 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 1285 |
+
"inputs": {
|
| 1286 |
+
"x": { "shape": [1, 512, 64, 64], "dtype": "float16", "dist": "normal", "seed": 354, "scale": 0.2 },
|
| 1287 |
+
"w": { "shape": [63, 512, 1, 1], "dtype": "float16", "dist": "normal", "seed": 355, "scale": 0.02 }
|
| 1288 |
+
},
|
| 1289 |
+
"outputs": { "y": { "shape": [1, 63, 64, 64], "dtype": "float16" } },
|
| 1290 |
+
"bench": {
|
| 1291 |
+
"metrics": [
|
| 1292 |
+
{
|
| 1293 |
+
"type": "gflops",
|
| 1294 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 1295 |
+
}
|
| 1296 |
+
]
|
| 1297 |
+
},
|
| 1298 |
+
"provenance": {
|
| 1299 |
+
"source": "synthetic",
|
| 1300 |
+
"notes": "Row-tile boundary sweep: full, partial, and device-dependent complete row bands share the same reduction and spatial extent."
|
| 1301 |
+
}
|
| 1302 |
+
},
|
| 1303 |
+
{
|
| 1304 |
+
"name": "direct-1x1-float16-m64-k512-n4096",
|
| 1305 |
+
"preset": "model",
|
| 1306 |
+
"vars": {
|
| 1307 |
+
"batch": 1,
|
| 1308 |
+
"inChannels": 512,
|
| 1309 |
+
"outChannels": 64,
|
| 1310 |
+
"inH": 64,
|
| 1311 |
+
"inW": 64,
|
| 1312 |
+
"kernelH": 1,
|
| 1313 |
+
"kernelW": 1,
|
| 1314 |
+
"strideH": 1,
|
| 1315 |
+
"strideW": 1,
|
| 1316 |
+
"padH": 0,
|
| 1317 |
+
"padW": 0
|
| 1318 |
+
},
|
| 1319 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 1320 |
+
"inputs": {
|
| 1321 |
+
"x": { "shape": [1, 512, 64, 64], "dtype": "float16", "dist": "normal", "seed": 354, "scale": 0.2 },
|
| 1322 |
+
"w": { "shape": [64, 512, 1, 1], "dtype": "float16", "dist": "normal", "seed": 355, "scale": 0.02 }
|
| 1323 |
+
},
|
| 1324 |
+
"outputs": { "y": { "shape": [1, 64, 64, 64], "dtype": "float16" } },
|
| 1325 |
+
"bench": {
|
| 1326 |
+
"metrics": [
|
| 1327 |
+
{
|
| 1328 |
+
"type": "gflops",
|
| 1329 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 1330 |
+
}
|
| 1331 |
+
]
|
| 1332 |
+
},
|
| 1333 |
+
"provenance": {
|
| 1334 |
+
"source": "synthetic",
|
| 1335 |
+
"notes": "Row-tile boundary sweep: full, partial, and device-dependent complete row bands share the same reduction and spatial extent."
|
| 1336 |
+
}
|
| 1337 |
+
},
|
| 1338 |
+
{
|
| 1339 |
+
"name": "direct-1x1-float16-m96-k512-n4096",
|
| 1340 |
+
"preset": "model",
|
| 1341 |
+
"vars": {
|
| 1342 |
+
"batch": 1,
|
| 1343 |
+
"inChannels": 512,
|
| 1344 |
+
"outChannels": 96,
|
| 1345 |
+
"inH": 64,
|
| 1346 |
+
"inW": 64,
|
| 1347 |
+
"kernelH": 1,
|
| 1348 |
+
"kernelW": 1,
|
| 1349 |
+
"strideH": 1,
|
| 1350 |
+
"strideW": 1,
|
| 1351 |
+
"padH": 0,
|
| 1352 |
+
"padW": 0
|
| 1353 |
+
},
|
| 1354 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 1355 |
+
"inputs": {
|
| 1356 |
+
"x": { "shape": [1, 512, 64, 64], "dtype": "float16", "dist": "normal", "seed": 354, "scale": 0.2 },
|
| 1357 |
+
"w": { "shape": [96, 512, 1, 1], "dtype": "float16", "dist": "normal", "seed": 355, "scale": 0.02 }
|
| 1358 |
+
},
|
| 1359 |
+
"outputs": { "y": { "shape": [1, 96, 64, 64], "dtype": "float16" } },
|
| 1360 |
+
"bench": {
|
| 1361 |
+
"metrics": [
|
| 1362 |
+
{
|
| 1363 |
+
"type": "gflops",
|
| 1364 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 1365 |
+
}
|
| 1366 |
+
]
|
| 1367 |
+
},
|
| 1368 |
+
"provenance": {
|
| 1369 |
+
"source": "synthetic",
|
| 1370 |
+
"notes": "Row-tile boundary sweep: full, partial, and device-dependent complete row bands share the same reduction and spatial extent."
|
| 1371 |
+
}
|
| 1372 |
}
|
| 1373 |
]
|
| 1374 |
}
|
build/webgpu/conv-1x1-gemm-tiled-reg.wgsl.jinja
CHANGED
|
@@ -1,11 +1,10 @@
|
|
| 1 |
-
//
|
| 2 |
-
//
|
| 3 |
-
//
|
| 4 |
-
//
|
| 5 |
-
//
|
| 6 |
-
//
|
| 7 |
-
//
|
| 8 |
-
// accumulator domain on store. Every dimension is bounds-checked.
|
| 9 |
// f16 operands remain packed in workgroup memory and widen only at the FMA site;
|
| 10 |
// the f32 accumulation order is retained while shared traffic is halved.
|
| 11 |
//
|
|
@@ -14,33 +13,35 @@
|
|
| 14 |
// N columns for one K. One micro-tile step therefore reads TM + 4 vector words
|
| 15 |
// instead of 4 * (TM + TN) scalars, so a shared word feeds four times as many
|
| 16 |
// FMAs and the K loop runs four accumulation steps per iteration.
|
| 17 |
-
{% if usesF16 %}
|
| 18 |
-
enable f16;
|
| 19 |
-
{% endif %}
|
| 20 |
{{ env.wgsl.resourceDeclarations }}
|
| 21 |
{% set hasActivation = hasActivation is defined and hasActivation %}
|
|
|
|
|
|
|
|
|
|
| 22 |
{% if hasActivation %}
|
| 23 |
// Apply the fused activation in the f32 accumulator before the single output
|
| 24 |
// cast, avoiding an intermediate convolution tensor.
|
| 25 |
-
|
| 26 |
-
{% if
|
| 27 |
return max(v, 0.0);
|
| 28 |
-
{% elif
|
| 29 |
-
return clamp(v, f32({{
|
| 30 |
-
{% elif
|
| 31 |
-
return select(v * f32({{
|
| 32 |
-
{% elif
|
| 33 |
return 1.0 / (1.0 + exp(-v));
|
| 34 |
-
{% elif
|
| 35 |
-
//
|
| 36 |
-
//
|
| 37 |
-
// already +/-1 to full f32 precision by |v| ~ 9.
|
| 38 |
return tanh(clamp(v, -10.0, 10.0));
|
| 39 |
-
{% elif
|
| 40 |
-
return clamp(f32({{
|
| 41 |
{% else %}
|
| 42 |
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 43 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
| 44 |
}
|
| 45 |
{% endif %}
|
| 46 |
|
|
@@ -67,19 +68,19 @@ const BN_VECS: u32 = BN / 4u;
|
|
| 67 |
// implicitIm2col leaves B unmaterialized. While staging a tile, the kernel
|
| 68 |
// decodes each (k, n) column-matrix coordinate into a raw NCHW input address.
|
| 69 |
// Overlapping windows may reread input values, trading address arithmetic and
|
| 70 |
-
//
|
| 71 |
{% set implicitIm2col = implicitIm2col is defined and implicitIm2col %}
|
| 72 |
{% set fusedNarrowProjection = fusedNarrowProjection is defined and fusedNarrowProjection %}
|
| 73 |
-
{%
|
| 74 |
-
{% set
|
| 75 |
-
{% set
|
| 76 |
-
{% set projectionOutputAct = outputActivation %}
|
| 77 |
-
{% set projectionScaled = hasOutputScale %}
|
| 78 |
-
{% set projectionBiased = hasProjectionBias %}
|
| 79 |
-
{% endif %}
|
| 80 |
{% set narrowProjectionTile = "tileB" if GEMM_BK >= GEMM_BM else "projectionTile" %}
|
| 81 |
{% set splitKValue = splitK if splitK is defined else 1 %}
|
| 82 |
{% set splitKPartial = splitKValue > 1 %}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
{% set gemmKLimit = "kEnd" if splitKPartial else "K" %}
|
| 84 |
{% set implicitGatherMode = tunables.IMPLICIT_TILED_GATHER_MODE if tunables.IMPLICIT_TILED_GATHER_MODE is defined else 0 %}
|
| 85 |
{% set implicitGatherWgSize = GEMM_WG_X * GEMM_WG_Y %}
|
|
@@ -105,11 +106,48 @@ const CONV_IN_W: u32 = {{ convInW }}u;
|
|
| 105 |
const CONV_OUT_W: u32 = {{ convOutW }}u;
|
| 106 |
const CONV_IN_CHANNELS: u32 = {{ convInChannels }}u;
|
| 107 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
{% if fusedNarrowProjection %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
|
| 111 |
const PROJECTION_OUT_C: u32 = {{ projectionChannels }}u;
|
| 112 |
-
{% else %}{% set emitConvStoreOut = not splitKPartial %}{%
|
|
|
|
| 113 |
if (m >= params.M || n >= params.N) {
|
| 114 |
return;
|
| 115 |
}
|
|
@@ -201,7 +239,7 @@ var<workgroup> projectionTile: array<array<vec4<f32>, BN_VECS>, BM>;
|
|
| 201 |
let bk = {{ rowBase }} + br;
|
| 202 |
var bvec = vec4<{{ tileT }}>({{ tileT }}(0.0));
|
| 203 |
if (bk < {{ rowLimit }}) {
|
| 204 |
-
// k
|
| 205 |
let ic = bk / CONV_KSIZE;
|
| 206 |
let kq = bk % CONV_KSIZE;
|
| 207 |
let kh = kq / CONV_KERNEL_W;
|
|
@@ -319,21 +357,41 @@ fn main(
|
|
| 319 |
let N = params.N;
|
| 320 |
let mBase = wg.y * BM;
|
| 321 |
let nBase = wg.x * BN;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
let batch = wg.z;
|
|
|
|
| 323 |
{% if not implicitIm2col %}
|
| 324 |
let xBatchBase = batch * K * N;
|
| 325 |
{% endif %}
|
| 326 |
let li = lid.y * WG_X + lid.x;
|
| 327 |
|
| 328 |
-
//
|
| 329 |
-
// independent FMA chains.
|
| 330 |
{% for row in range(GEMM_TM) %}
|
| 331 |
var acc{{ row }} = vec{{ GEMM_TN }}<f32>(0.0);
|
| 332 |
{% endfor %}
|
| 333 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
let numTiles = (K + BK - 1u) / BK;
|
|
|
|
| 335 |
for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
|
|
|
|
|
|
|
|
|
|
| 336 |
let kBase = kt * BK;
|
|
|
|
| 337 |
{{ load_a_vec4("mBase", "kBase", "M", gemmKLimit) }}
|
| 338 |
{% if useCarriedImplicitGather %}
|
| 339 |
{{ load_b_implicit_carried("kBase", "nBase", gemmKLimit, "N") }}
|
|
@@ -360,14 +418,22 @@ fn main(
|
|
| 360 |
workgroupBarrier();
|
| 361 |
}
|
| 362 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
{% if not fusedNarrowProjection %}
|
| 364 |
let yBatchBase = batch * M * N;
|
| 365 |
{% endif %}
|
| 366 |
let m0 = mBase + lid.y * TM;
|
| 367 |
let n0 = nBase + lid.x * TN;
|
|
|
|
| 368 |
{% for row in range(GEMM_TM) %}
|
| 369 |
{% for column in range(GEMM_TN) %}
|
| 370 |
-
{% if
|
|
|
|
|
|
|
| 371 |
{{ publish_projection_input("lid.y * TM + " ~ row ~ "u", "lid.x * " ~ ((GEMM_TN / 4)|int) ~ "u + " ~ ((column / 4)|int) ~ "u", components[column % 4], "m0 + " ~ row ~ "u", "n0 + " ~ column ~ "u", "acc" ~ row ~ "." ~ components[column]) }}
|
| 372 |
{% else %}
|
| 373 |
store_out(m0 + {{ row }}u, n0 + {{ column }}u, yBatchBase, acc{{ row }}.{{ components[column] }});
|
|
@@ -396,7 +462,7 @@ fn main(
|
|
| 396 |
|
| 397 |
let projectionYBase = batch * PROJECTION_OUT_C * N;
|
| 398 |
{% for oc in range(projectionChannels) %}
|
| 399 |
-
let projected{{ oc }} = projectionAcc{{ oc }}
|
| 400 |
{% endfor %}
|
| 401 |
{% for column in range(4) %}
|
| 402 |
let projectionN{{ column }} = projectionNBase + {{ column }}u;
|
|
@@ -409,11 +475,7 @@ fn main(
|
|
| 409 |
{% else %}
|
| 410 |
let activated{{ oc }}_{{ column }} = projected{{ oc }}.{{ components[column] }};
|
| 411 |
{% endif %}
|
| 412 |
-
{% if projectionScaled %}
|
| 413 |
-
y[projectionYBase + {{ oc }}u * N + projectionN{{ column }}] = activated{{ oc }}_{{ column }} * params.outputScale;
|
| 414 |
-
{% else %}
|
| 415 |
y[projectionYBase + {{ oc }}u * N + projectionN{{ column }}] = activated{{ oc }}_{{ column }};
|
| 416 |
-
{% endif %}
|
| 417 |
{% endfor %}
|
| 418 |
}
|
| 419 |
{% endfor %}
|
|
|
|
| 1 |
+
// Register-blocked batched matrix product:
|
| 2 |
+
// C[b] (M x N) = A (M x K) * B[b] (K x N). Workgroup lanes compute TM x TN
|
| 3 |
+
// micro-tiles. The defaults use a 16x8 lane grid with an 8x4 micro-tile over a
|
| 4 |
+
// 64x64 output tile. A is shared across the batch; B and C use the dispatch.z
|
| 5 |
+
// batch offset. Accumulation is f32; an optional epilogue applies configured
|
| 6 |
+
// bias, residual, and activation terms in the accumulator domain on store.
|
| 7 |
+
// Every matrix dimension is bounds-checked.
|
|
|
|
| 8 |
// f16 operands remain packed in workgroup memory and widen only at the FMA site;
|
| 9 |
// the f32 accumulation order is retained while shared traffic is halved.
|
| 10 |
//
|
|
|
|
| 13 |
// N columns for one K. One micro-tile step therefore reads TM + 4 vector words
|
| 14 |
// instead of 4 * (TM + TN) scalars, so a shared word feeds four times as many
|
| 15 |
// FMAs and the K loop runs four accumulation steps per iteration.
|
|
|
|
|
|
|
|
|
|
| 16 |
{{ env.wgsl.resourceDeclarations }}
|
| 17 |
{% set hasActivation = hasActivation is defined and hasActivation %}
|
| 18 |
+
{% set activation = activation | default("") %}
|
| 19 |
+
{% set actAlpha = actAlpha | default(0.0) %}
|
| 20 |
+
{% set actBeta = actBeta | default(0.0) %}
|
| 21 |
{% if hasActivation %}
|
| 22 |
// Apply the fused activation in the f32 accumulator before the single output
|
| 23 |
// cast, avoiding an intermediate convolution tensor.
|
| 24 |
+
{% macro fused_act_return(mode, alpha, beta) -%}
|
| 25 |
+
{% if mode == "Relu" %}
|
| 26 |
return max(v, 0.0);
|
| 27 |
+
{% elif mode == "Clip" %}
|
| 28 |
+
return clamp(v, f32({{ alpha }}), f32({{ beta }}));
|
| 29 |
+
{% elif mode == "LeakyRelu" %}
|
| 30 |
+
return select(v * f32({{ alpha }}), v, v >= 0.0);
|
| 31 |
+
{% elif mode == "Sigmoid" %}
|
| 32 |
return 1.0 / (1.0 + exp(-v));
|
| 33 |
+
{% elif mode == "Tanh" %}
|
| 34 |
+
// tanh is already +/-1 to f32 precision at the clamp bounds. Clamping before
|
| 35 |
+
// the builtin preserves that saturated result for larger accumulators.
|
|
|
|
| 36 |
return tanh(clamp(v, -10.0, 10.0));
|
| 37 |
+
{% elif mode == "HardSigmoid" %}
|
| 38 |
+
return clamp(f32({{ alpha }}) * v + f32({{ beta }}), 0.0, 1.0);
|
| 39 |
{% else %}
|
| 40 |
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 41 |
{% endif %}
|
| 42 |
+
{%- endmacro -%}
|
| 43 |
+
fn fused_act(v: f32) -> f32 {
|
| 44 |
+
{{ fused_act_return(activation, actAlpha, actBeta) -}}
|
| 45 |
}
|
| 46 |
{% endif %}
|
| 47 |
|
|
|
|
| 68 |
// implicitIm2col leaves B unmaterialized. While staging a tile, the kernel
|
| 69 |
// decodes each (k, n) column-matrix coordinate into a raw NCHW input address.
|
| 70 |
// Overlapping windows may reread input values, trading address arithmetic and
|
| 71 |
+
// repeated input loads for writing and rereading an expanded column matrix.
|
| 72 |
{% set implicitIm2col = implicitIm2col is defined and implicitIm2col %}
|
| 73 |
{% set fusedNarrowProjection = fusedNarrowProjection is defined and fusedNarrowProjection %}
|
| 74 |
+
{% set projectionChannels = projectionOutChannels | default(0) %}
|
| 75 |
+
{% set projectionInputAct = inputActivation | default("none") %}
|
| 76 |
+
{% set projectionOutputAct = outputActivation | default("none") %}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
{% set narrowProjectionTile = "tileB" if GEMM_BK >= GEMM_BM else "projectionTile" %}
|
| 78 |
{% set splitKValue = splitK if splitK is defined else 1 %}
|
| 79 |
{% set splitKPartial = splitKValue > 1 %}
|
| 80 |
+
{% set kTiles = kTiles | default(0) %}
|
| 81 |
+
{% set mPadded = mPadded | default(0) %}
|
| 82 |
+
{% set nPadded = nPadded | default(0) %}
|
| 83 |
+
{% set batchCount = batchCount | default(0) %}
|
| 84 |
{% set gemmKLimit = "kEnd" if splitKPartial else "K" %}
|
| 85 |
{% set implicitGatherMode = tunables.IMPLICIT_TILED_GATHER_MODE if tunables.IMPLICIT_TILED_GATHER_MODE is defined else 0 %}
|
| 86 |
{% set implicitGatherWgSize = GEMM_WG_X * GEMM_WG_Y %}
|
|
|
|
| 106 |
const CONV_OUT_W: u32 = {{ convOutW }}u;
|
| 107 |
const CONV_IN_CHANNELS: u32 = {{ convInChannels }}u;
|
| 108 |
{% endif %}
|
| 109 |
+
{% if splitKPartial %}
|
| 110 |
+
// Split-K publishes raw f32 partials. A second pass owns the bias, optional
|
| 111 |
+
// epilogue, output cast, and logical-shape store.
|
| 112 |
+
const SPLIT_K: u32 = {{ splitKValue }}u;
|
| 113 |
+
const K_TILES: u32 = {{ kTiles }}u;
|
| 114 |
+
const PARTIAL_ROWS: u32 = {{ mPadded }}u;
|
| 115 |
+
const PARTIAL_COLS: u32 = {{ nPadded }}u;
|
| 116 |
+
const PARTIAL_BATCH_STRIDE: u32 = PARTIAL_ROWS * PARTIAL_COLS;
|
| 117 |
+
const PARTIAL_SLICE_STRIDE: u32 = {{ batchCount }}u * PARTIAL_BATCH_STRIDE;
|
| 118 |
+
{% endif %}
|
| 119 |
|
| 120 |
{% if fusedNarrowProjection %}
|
| 121 |
+
{% set inputActivation = inputActivation | default("none") %}
|
| 122 |
+
{% set outputActivation = outputActivation | default("none") %}
|
| 123 |
+
{% if inputActivation == "relu" or outputActivation == "relu" %}
|
| 124 |
+
fn is_nan_f32(value: f32) -> bool {
|
| 125 |
+
let bits = bitcast<u32>(value);
|
| 126 |
+
return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
fn projection_relu(value: f32) -> f32 {
|
| 130 |
+
var out = max(value, 0.0);
|
| 131 |
+
if (is_nan_f32(value)) {
|
| 132 |
+
out = value;
|
| 133 |
+
}
|
| 134 |
+
return out;
|
| 135 |
+
}
|
| 136 |
+
{% endif %}
|
| 137 |
+
{% if outputActivation == "sigmoid" %}
|
| 138 |
+
fn sigmoid_safe(x: f32) -> f32 {
|
| 139 |
+
if (x >= 0.0) {
|
| 140 |
+
let z = exp(-x);
|
| 141 |
+
return 1.0 / (1.0 + z);
|
| 142 |
+
}
|
| 143 |
+
let z = exp(x);
|
| 144 |
+
return z / (1.0 + z);
|
| 145 |
+
}
|
| 146 |
+
{% endif %}
|
| 147 |
|
| 148 |
const PROJECTION_OUT_C: u32 = {{ projectionChannels }}u;
|
| 149 |
+
{% else %}{% set emitConvStoreOut = not splitKPartial %}{% set hasZ = hasZ is defined and hasZ %}
|
| 150 |
+
{% if emitConvStoreOut | default(true) %}fn store_out(m: u32, n: u32, yBase: u32, raw: f32) {
|
| 151 |
if (m >= params.M || n >= params.N) {
|
| 152 |
return;
|
| 153 |
}
|
|
|
|
| 239 |
let bk = {{ rowBase }} + br;
|
| 240 |
var bvec = vec4<{{ tileT }}>({{ tileT }}(0.0));
|
| 241 |
if (bk < {{ rowLimit }}) {
|
| 242 |
+
// k flattens the input-channel and spatial-tap axes; n is the output position.
|
| 243 |
let ic = bk / CONV_KSIZE;
|
| 244 |
let kq = bk % CONV_KSIZE;
|
| 245 |
let kh = kq / CONV_KERNEL_W;
|
|
|
|
| 357 |
let N = params.N;
|
| 358 |
let mBase = wg.y * BM;
|
| 359 |
let nBase = wg.x * BN;
|
| 360 |
+
{% if splitKPartial %}
|
| 361 |
+
// z carries a K slice within a batch image, matching the subgroup-matrix
|
| 362 |
+
// Conv split-K layout and the shared reduction pass.
|
| 363 |
+
let slice = wg.z % SPLIT_K;
|
| 364 |
+
let batch = wg.z / SPLIT_K;
|
| 365 |
+
{% else %}
|
| 366 |
let batch = wg.z;
|
| 367 |
+
{% endif %}
|
| 368 |
{% if not implicitIm2col %}
|
| 369 |
let xBatchBase = batch * K * N;
|
| 370 |
{% endif %}
|
| 371 |
let li = lid.y * WG_X + lid.x;
|
| 372 |
|
| 373 |
+
// The statically named accumulator vectors form TM independent FMA chains.
|
|
|
|
| 374 |
{% for row in range(GEMM_TM) %}
|
| 375 |
var acc{{ row }} = vec{{ GEMM_TN }}<f32>(0.0);
|
| 376 |
{% endfor %}
|
| 377 |
|
| 378 |
+
{% if splitKPartial %}
|
| 379 |
+
// Partition whole BK-wide tiles by quotient boundaries. This keeps every
|
| 380 |
+
// slice within one tile of every other slice and creates no empty tail slice
|
| 381 |
+
// whenever K_TILES >= SPLIT_K (this route requires a stronger floor).
|
| 382 |
+
let kBegin = (slice * K_TILES / SPLIT_K) * BK;
|
| 383 |
+
let kEnd = min(((slice + 1u) * K_TILES / SPLIT_K) * BK, K);
|
| 384 |
+
let kCount = kEnd - kBegin;
|
| 385 |
+
let numTiles = (kCount + BK - 1u) / BK;
|
| 386 |
+
{% else %}
|
| 387 |
let numTiles = (K + BK - 1u) / BK;
|
| 388 |
+
{% endif %}
|
| 389 |
for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
|
| 390 |
+
{% if splitKPartial %}
|
| 391 |
+
let kBase = kBegin + kt * BK;
|
| 392 |
+
{% else %}
|
| 393 |
let kBase = kt * BK;
|
| 394 |
+
{% endif %}
|
| 395 |
{{ load_a_vec4("mBase", "kBase", "M", gemmKLimit) }}
|
| 396 |
{% if useCarriedImplicitGather %}
|
| 397 |
{{ load_b_implicit_carried("kBase", "nBase", gemmKLimit, "N") }}
|
|
|
|
| 418 |
workgroupBarrier();
|
| 419 |
}
|
| 420 |
|
| 421 |
+
{% if splitKPartial %}
|
| 422 |
+
let m0 = mBase + lid.y * TM;
|
| 423 |
+
let n0 = nBase + lid.x * TN;
|
| 424 |
+
let partialBase = slice * PARTIAL_SLICE_STRIDE + batch * PARTIAL_BATCH_STRIDE;
|
| 425 |
+
{% else %}
|
| 426 |
{% if not fusedNarrowProjection %}
|
| 427 |
let yBatchBase = batch * M * N;
|
| 428 |
{% endif %}
|
| 429 |
let m0 = mBase + lid.y * TM;
|
| 430 |
let n0 = nBase + lid.x * TN;
|
| 431 |
+
{% endif %}
|
| 432 |
{% for row in range(GEMM_TM) %}
|
| 433 |
{% for column in range(GEMM_TN) %}
|
| 434 |
+
{% if splitKPartial %}
|
| 435 |
+
y[partialBase + (m0 + {{ row }}u) * PARTIAL_COLS + n0 + {{ column }}u] = acc{{ row }}.{{ components[column] }};
|
| 436 |
+
{% elif fusedNarrowProjection %}
|
| 437 |
{{ publish_projection_input("lid.y * TM + " ~ row ~ "u", "lid.x * " ~ ((GEMM_TN / 4)|int) ~ "u + " ~ ((column / 4)|int) ~ "u", components[column % 4], "m0 + " ~ row ~ "u", "n0 + " ~ column ~ "u", "acc" ~ row ~ "." ~ components[column]) }}
|
| 438 |
{% else %}
|
| 439 |
store_out(m0 + {{ row }}u, n0 + {{ column }}u, yBatchBase, acc{{ row }}.{{ components[column] }});
|
|
|
|
| 462 |
|
| 463 |
let projectionYBase = batch * PROJECTION_OUT_C * N;
|
| 464 |
{% for oc in range(projectionChannels) %}
|
| 465 |
+
let projected{{ oc }} = projectionAcc{{ oc }};
|
| 466 |
{% endfor %}
|
| 467 |
{% for column in range(4) %}
|
| 468 |
let projectionN{{ column }} = projectionNBase + {{ column }}u;
|
|
|
|
| 475 |
{% else %}
|
| 476 |
let activated{{ oc }}_{{ column }} = projected{{ oc }}.{{ components[column] }};
|
| 477 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
| 478 |
y[projectionYBase + {{ oc }}u * N + projectionN{{ column }}] = activated{{ oc }}_{{ column }};
|
|
|
|
| 479 |
{% endfor %}
|
| 480 |
}
|
| 481 |
{% endfor %}
|
build/webgpu/conv-1x1-gemm-tiled.wgsl.jinja
CHANGED
|
@@ -1,46 +1,39 @@
|
|
| 1 |
-
//
|
| 2 |
-
//
|
| 3 |
-
//
|
| 4 |
-
//
|
| 5 |
-
//
|
| 6 |
-
//
|
| 7 |
-
//
|
| 8 |
-
// micro-tile, with a selectable K tile (16 by default), the same structure as
|
| 9 |
-
// the general tiled matmul path — each shared element feeds 2 FMAs
|
| 10 |
-
// and a 32-wide column tile is staged once instead of re-reading W per output
|
| 11 |
-
// column. The weight matrix (A) is shared across the batch (no batch stride);
|
| 12 |
-
// the input (B) and output carry the batch offset via dispatch.z. f32
|
| 13 |
-
// accumulation, cast to the output element type on store; the optional bias is
|
| 14 |
-
// per output channel (M row), added after the full K accumulation per ONNX Conv
|
| 15 |
-
// semantics. f16 operands stay packed in workgroup memory and widen only when
|
| 16 |
-
// consumed. Fully bounds-checked (any M/N/K).
|
| 17 |
-
{% if usesF16 %}
|
| 18 |
-
enable f16;
|
| 19 |
-
{% endif %}
|
| 20 |
{{ env.wgsl.resourceDeclarations }}
|
| 21 |
{% set hasActivation = hasActivation is defined and hasActivation %}
|
|
|
|
|
|
|
|
|
|
| 22 |
{% if hasActivation %}
|
| 23 |
// Apply the fused activation in the f32 accumulator before the single output
|
| 24 |
// cast, avoiding an intermediate convolution tensor.
|
| 25 |
-
|
| 26 |
-
{% if
|
| 27 |
return max(v, 0.0);
|
| 28 |
-
{% elif
|
| 29 |
-
return clamp(v, f32({{
|
| 30 |
-
{% elif
|
| 31 |
-
return select(v * f32({{
|
| 32 |
-
{% elif
|
| 33 |
return 1.0 / (1.0 + exp(-v));
|
| 34 |
-
{% elif
|
| 35 |
-
//
|
| 36 |
-
//
|
| 37 |
-
// already +/-1 to full f32 precision by |v| ~ 9.
|
| 38 |
return tanh(clamp(v, -10.0, 10.0));
|
| 39 |
-
{% elif
|
| 40 |
-
return clamp(f32({{
|
| 41 |
{% else %}
|
| 42 |
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 43 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
| 44 |
}
|
| 45 |
{% endif %}
|
| 46 |
|
|
@@ -49,8 +42,9 @@ const BK: u32 = 16u;
|
|
| 49 |
const BM: u32 = 32u;
|
| 50 |
const BN: u32 = 32u;
|
| 51 |
|
| 52 |
-
// Store one output element with the
|
| 53 |
-
//
|
|
|
|
| 54 |
fn store_out(m: u32, n: u32, yBase: u32, raw: f32) {
|
| 55 |
if (m >= params.M || n >= params.N) {
|
| 56 |
return;
|
|
|
|
| 1 |
+
// Tiled batched matrix product C[b] (M x N) = A (M x K) * B[b] (K x N).
|
| 2 |
+
// A 16x16 workgroup computes a 32x32 output tile with a 2x2 micro-tile per
|
| 3 |
+
// thread and a 16-element K tile. A is shared across the batch; B and C use the
|
| 4 |
+
// dispatch.z batch offset. Accumulation is f32 and is cast to the output type on
|
| 5 |
+
// store. A configured epilogue may add bias, residual, and activation terms.
|
| 6 |
+
// f16 operands remain packed in workgroup memory and widen when consumed. All
|
| 7 |
+
// M, N, and K accesses are bounds-checked.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
{{ env.wgsl.resourceDeclarations }}
|
| 9 |
{% set hasActivation = hasActivation is defined and hasActivation %}
|
| 10 |
+
{% set activation = activation | default("") %}
|
| 11 |
+
{% set actAlpha = actAlpha | default(0.0) %}
|
| 12 |
+
{% set actBeta = actBeta | default(0.0) %}
|
| 13 |
{% if hasActivation %}
|
| 14 |
// Apply the fused activation in the f32 accumulator before the single output
|
| 15 |
// cast, avoiding an intermediate convolution tensor.
|
| 16 |
+
{% macro fused_act_return(mode, alpha, beta) -%}
|
| 17 |
+
{% if mode == "Relu" %}
|
| 18 |
return max(v, 0.0);
|
| 19 |
+
{% elif mode == "Clip" %}
|
| 20 |
+
return clamp(v, f32({{ alpha }}), f32({{ beta }}));
|
| 21 |
+
{% elif mode == "LeakyRelu" %}
|
| 22 |
+
return select(v * f32({{ alpha }}), v, v >= 0.0);
|
| 23 |
+
{% elif mode == "Sigmoid" %}
|
| 24 |
return 1.0 / (1.0 + exp(-v));
|
| 25 |
+
{% elif mode == "Tanh" %}
|
| 26 |
+
// tanh is already +/-1 to f32 precision at the clamp bounds. Clamping before
|
| 27 |
+
// the builtin preserves that saturated result for larger accumulators.
|
|
|
|
| 28 |
return tanh(clamp(v, -10.0, 10.0));
|
| 29 |
+
{% elif mode == "HardSigmoid" %}
|
| 30 |
+
return clamp(f32({{ alpha }}) * v + f32({{ beta }}), 0.0, 1.0);
|
| 31 |
{% else %}
|
| 32 |
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 33 |
{% endif %}
|
| 34 |
+
{%- endmacro -%}
|
| 35 |
+
fn fused_act(v: f32) -> f32 {
|
| 36 |
+
{{ fused_act_return(activation, actAlpha, actBeta) -}}
|
| 37 |
}
|
| 38 |
{% endif %}
|
| 39 |
|
|
|
|
| 42 |
const BM: u32 = 32u;
|
| 43 |
const BN: u32 = 32u;
|
| 44 |
|
| 45 |
+
// Store one output element with the configured bias/residual/activation
|
| 46 |
+
// epilogue in the f32 accumulator domain.
|
| 47 |
+
{% set hasZ = hasZ is defined and hasZ %}
|
| 48 |
fn store_out(m: u32, n: u32, yBase: u32, raw: f32) {
|
| 49 |
if (m >= params.M || n >= params.N) {
|
| 50 |
return;
|
build/webgpu/conv-1x1-subgroup-matrix.wgsl.jinja
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
-
//
|
| 2 |
-
// is
|
| 3 |
-
//
|
|
|
|
| 4 |
//
|
| 5 |
// Full 8x8 matrix loads require complete K/N tiles. The aligned path enforces
|
| 6 |
// that geometry; `padded` zero-fills tails, and `implicitIm2col` gathers and
|
|
@@ -10,15 +11,41 @@
|
|
| 10 |
// sums to tile-padded [splitK,batch,M_PAD,N_PAD] scratch; a later pass combines
|
| 11 |
// the slices and applies bias or an epilogue. Padded tail cells remain zero and
|
| 12 |
// are never copied to the logical output.
|
|
|
|
| 13 |
{% set fusedNarrowProjection = fusedNarrowProjection if fusedNarrowProjection is defined else false %}
|
| 14 |
{% set polyphase = polyphaseConvTranspose is defined and polyphaseConvTranspose %}
|
|
|
|
|
|
|
| 15 |
{% set splitKValue = splitK if splitK is defined else 1 %}
|
| 16 |
{% set splitKPartial = splitKValue > 1 %}
|
| 17 |
{% set kLoopVar = "K_LOOP" if padded else "K" %}
|
| 18 |
{% set nColsVar = "N_COLS" if padded else "N" %}
|
| 19 |
-
{%
|
| 20 |
-
|
| 21 |
-
{%
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
enable subgroups;
|
| 23 |
{% if pinSubgroupSize32 %}
|
| 24 |
enable subgroup_size_control;
|
|
@@ -26,7 +53,35 @@ enable subgroup_size_control;
|
|
| 26 |
enable chromium_experimental_subgroup_matrix;
|
| 27 |
diagnostic(off, chromium.subgroup_matrix_uniformity);
|
| 28 |
|
|
|
|
| 29 |
{{ env.wgsl.resourceDeclarations }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
{% set operandScalar = fScalar %}
|
| 32 |
{% set accScalar = "f32" %}
|
|
@@ -43,29 +98,78 @@ diagnostic(off, chromium.subgroup_matrix_uniformity);
|
|
| 43 |
|
| 44 |
const M: u32 = {{ M }}u;
|
| 45 |
const K: u32 = {{ K }}u;
|
|
|
|
| 46 |
const N: u32 = {{ N }}u;
|
|
|
|
| 47 |
{% if padded %}
|
| 48 |
const K_LOOP: u32 = {{ kPadded }}u;
|
| 49 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
{% if padded %}
|
| 51 |
const N_COLS: u32 = {{ nPadded }}u;
|
| 52 |
{% endif %}
|
| 53 |
const B_BATCH_STRIDE: u32 = {{ kLoopVar }} * {{ nColsVar }};
|
|
|
|
| 54 |
{% if not splitKPartial and not polyphase and not fusedNarrowProjection %}
|
| 55 |
const C_BATCH_STRIDE: u32 = M * N;
|
| 56 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
const TILE_COLS: u32 = {{ tileColsValue }}u;
|
| 58 |
const TILE_ROWS: u32 = {{ tileRowsValue }}u;
|
| 59 |
const TILE_K: u32 = 32u;
|
| 60 |
const SUB_COLS: u32 = {{ subColsValue }}u;
|
| 61 |
const SUB_ROWS: u32 = {{ subRowsValue }}u;
|
| 62 |
|
|
|
|
| 63 |
var<workgroup> tile_A: array<{{ operandScalar }}, {{ tileRowsValue }} * 32>;
|
| 64 |
var<workgroup> tile_B: array<{{ operandScalar }}, {{ tileColsValue }} * 32>;
|
|
|
|
| 65 |
{% if (not useDirectMatrixStore or padded) and not splitKPartial and not fusedNarrowProjection %}
|
| 66 |
var<workgroup> scratch: array<array<array<{{ accScalar }}, 64>, 4>, {{ (workgroupThreadsValue / 32)|int }}>;
|
| 67 |
|
| 68 |
{% endif %}
|
|
|
|
| 69 |
fn loadSHMA(tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
|
| 70 |
let a_global = tile_base + row;
|
| 71 |
let col = c_idx * 8u;
|
|
@@ -91,6 +195,126 @@ fn loadSHMA(tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
|
|
| 91 |
fn loadSHMB(b_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
|
| 92 |
let b_col = tile_base + row;
|
| 93 |
let col = c_idx * {{ bLoadWidth }}u;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
for (var i = 0u; i < {{ bLoadWidth }}u; i = i + 1u) {
|
| 95 |
let k = k_idx + col + i;
|
| 96 |
{% if operandScalar == "f16" %}
|
|
@@ -99,31 +323,38 @@ fn loadSHMB(b_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
|
|
| 99 |
tile_B[row * TILE_K + col + i] = f32(xm[b_base + k * {{ nColsVar }} + b_col]);
|
| 100 |
{% endif %}
|
| 101 |
}
|
|
|
|
| 102 |
}
|
| 103 |
|
|
|
|
| 104 |
{% set hasActivation = hasActivation is defined and hasActivation %}
|
|
|
|
|
|
|
|
|
|
| 105 |
{% if hasActivation %}
|
| 106 |
// Apply the fused activation in the f32 accumulator before the single output
|
| 107 |
// cast, avoiding an intermediate convolution tensor.
|
| 108 |
-
|
| 109 |
-
{% if
|
| 110 |
return max(v, 0.0);
|
| 111 |
-
{% elif
|
| 112 |
-
return clamp(v, f32({{
|
| 113 |
-
{% elif
|
| 114 |
-
return select(v * f32({{
|
| 115 |
-
{% elif
|
| 116 |
return 1.0 / (1.0 + exp(-v));
|
| 117 |
-
{% elif
|
| 118 |
-
//
|
| 119 |
-
//
|
| 120 |
-
// already +/-1 to full f32 precision by |v| ~ 9.
|
| 121 |
return tanh(clamp(v, -10.0, 10.0));
|
| 122 |
-
{% elif
|
| 123 |
-
return clamp(f32({{
|
| 124 |
{% else %}
|
| 125 |
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 126 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
| 127 |
}
|
| 128 |
{% endif %}
|
| 129 |
|
|
@@ -191,10 +422,18 @@ fn main(
|
|
| 191 |
{% endif %}
|
| 192 |
@builtin(subgroup_size) sg_size: u32
|
| 193 |
) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
let batch = workgroup_id.z;
|
| 195 |
let b_base = batch * B_BATCH_STRIDE;
|
| 196 |
{% if not fusedNarrowProjection %}
|
| 197 |
let c_base = batch * C_BATCH_STRIDE;
|
|
|
|
| 198 |
{% endif %}
|
| 199 |
let a_global_base = workgroup_id.y * TILE_ROWS;
|
| 200 |
let b_global_base = workgroup_id.x * TILE_COLS;
|
|
@@ -213,21 +452,30 @@ fn main(
|
|
| 213 |
var matC12: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
|
| 214 |
var matC13: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
|
| 215 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
for (var kidx = 0u; kidx < {{ kLoopVar }}; kidx = kidx + TILE_K) {
|
|
|
|
|
|
|
| 217 |
loadSHMA(a_global_base, kidx, local_idx / 4u, local_idx % 4u);
|
| 218 |
loadSHMB(b_base, b_global_base, kidx, local_idx / {{ bKChunks }}u, local_idx % {{ bKChunks }}u);
|
| 219 |
workgroupBarrier();
|
|
|
|
| 220 |
|
| 221 |
for (var step = 0u; step < TILE_K; step = step + 8u) {
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
|
|
|
|
|
|
| 225 |
|
| 226 |
-
let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
|
| 227 |
-
|
| 228 |
-
var
|
| 229 |
-
|
| 230 |
-
var matB3: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 24u * TILE_K, TILE_K);
|
| 231 |
|
| 232 |
matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
|
| 233 |
matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
|
|
@@ -238,7 +486,9 @@ fn main(
|
|
| 238 |
matC12 = subgroupMatrixMultiplyAccumulate(matA1, matB2, matC12);
|
| 239 |
matC13 = subgroupMatrixMultiplyAccumulate(matA1, matB3, matC13);
|
| 240 |
}
|
|
|
|
| 241 |
workgroupBarrier();
|
|
|
|
| 242 |
}
|
| 243 |
|
| 244 |
{% if fusedNarrowProjection %}
|
|
@@ -268,7 +518,11 @@ fn main(
|
|
| 268 |
{% endfor %}
|
| 269 |
for (var channel = 0u; channel < M; channel = channel + 1u) {
|
| 270 |
let producer_value = tile_B[channel * TILE_COLS + local_idx]{% if hasBias %} + f32(bias[channel]){% endif %};
|
|
|
|
|
|
|
|
|
|
| 271 |
let activated_value = producer_value;
|
|
|
|
| 272 |
{% for oc in range(projectionOutChannels) %}
|
| 273 |
projected_acc{{ oc }} = projected_acc{{ oc }} + activated_value * f32(projectionW[{{ oc }}u * M + channel]);
|
| 274 |
{% endfor %}
|
|
@@ -276,11 +530,30 @@ fn main(
|
|
| 276 |
|
| 277 |
let fused_y_base = batch * {{ projectionOutChannels }}u * N + fused_global_col;
|
| 278 |
{% for oc in range(projectionOutChannels) %}
|
| 279 |
-
let projected{{ oc }} = projected_acc{{ oc }};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
let activated{{ oc }} = projected{{ oc }};
|
| 281 |
-
|
|
|
|
| 282 |
{% endfor %}
|
| 283 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
{% elif useDirectMatrixStore %}
|
| 285 |
{% if padded %}
|
| 286 |
// Padded N only needs guarded publication for the final partial-width tile.
|
|
|
|
| 1 |
+
// Batched subgroup-matrix product C[b] (M x N) = A (M x K) * B[b] (K x N).
|
| 2 |
+
// A is row-major; B may be a direct matrix view, a materialized column matrix,
|
| 3 |
+
// or an on-demand gather. Accumulation is f32. A configured epilogue may apply
|
| 4 |
+
// bias or other output transforms after the full K reduction.
|
| 5 |
//
|
| 6 |
// Full 8x8 matrix loads require complete K/N tiles. The aligned path enforces
|
| 7 |
// that geometry; `padded` zero-fills tails, and `implicitIm2col` gathers and
|
|
|
|
| 11 |
// sums to tile-padded [splitK,batch,M_PAD,N_PAD] scratch; a later pass combines
|
| 12 |
// the slices and applies bias or an epilogue. Padded tail cells remain zero and
|
| 13 |
// are never copied to the logical output.
|
| 14 |
+
{% set directMatrixInputs = directMatrixInputs is defined and directMatrixInputs %}
|
| 15 |
{% set fusedNarrowProjection = fusedNarrowProjection if fusedNarrowProjection is defined else false %}
|
| 16 |
{% set polyphase = polyphaseConvTranspose is defined and polyphaseConvTranspose %}
|
| 17 |
+
{% set implicit = implicitIm2col is defined and implicitIm2col %}
|
| 18 |
+
{% set implicit3d = implicit and (convSpatialDims is defined and convSpatialDims == 3) %}
|
| 19 |
{% set splitKValue = splitK if splitK is defined else 1 %}
|
| 20 |
{% set splitKPartial = splitKValue > 1 %}
|
| 21 |
{% set kLoopVar = "K_LOOP" if padded else "K" %}
|
| 22 |
{% set nColsVar = "N_COLS" if padded else "N" %}
|
| 23 |
+
{% set hasZ = hasZ is defined and hasZ %}
|
| 24 |
+
{% set projectionOutChannels = projectionOutChannels | default(0) %}
|
| 25 |
+
{% set hasProjectionBias = hasProjectionBias is defined and hasProjectionBias %}
|
| 26 |
+
{% set hasOutputScale = hasOutputScale is defined and hasOutputScale %}
|
| 27 |
+
{% set convKernelH = convKernelH | default(0) %}
|
| 28 |
+
{% set convKernelW = convKernelW | default(0) %}
|
| 29 |
+
{% set convStrideH = convStrideH | default(0) %}
|
| 30 |
+
{% set convStrideW = convStrideW | default(0) %}
|
| 31 |
+
{% set convDilationH = convDilationH | default(0) %}
|
| 32 |
+
{% set convDilationW = convDilationW | default(0) %}
|
| 33 |
+
{% set convPadTop = convPadTop | default(0) %}
|
| 34 |
+
{% set convPadLeft = convPadLeft | default(0) %}
|
| 35 |
+
{% set convInH = convInH | default(0) %}
|
| 36 |
+
{% set convInW = convInW | default(0) %}
|
| 37 |
+
{% set convOutW = convOutW | default(0) %}
|
| 38 |
+
{% set convInChannels = convInChannels | default(0) %}
|
| 39 |
+
{% set convKernelD = convKernelD | default(0) %}
|
| 40 |
+
{% set convStrideD = convStrideD | default(0) %}
|
| 41 |
+
{% set convDilationD = convDilationD | default(0) %}
|
| 42 |
+
{% set convPadFront = convPadFront | default(0) %}
|
| 43 |
+
{% set convInD = convInD | default(0) %}
|
| 44 |
+
{% set convOutH = convOutH | default(0) %}
|
| 45 |
+
{% set nPadded = nPadded | default(0) %}
|
| 46 |
+
{% set mPadded = mPadded | default(0) %}
|
| 47 |
+
{% set kChunk = kChunk | default(0) %}
|
| 48 |
+
{% set batchCount = batchCount | default(0) %}
|
| 49 |
enable subgroups;
|
| 50 |
{% if pinSubgroupSize32 %}
|
| 51 |
enable subgroup_size_control;
|
|
|
|
| 53 |
enable chromium_experimental_subgroup_matrix;
|
| 54 |
diagnostic(off, chromium.subgroup_matrix_uniformity);
|
| 55 |
|
| 56 |
+
|
| 57 |
{{ env.wgsl.resourceDeclarations }}
|
| 58 |
+
{% if fusedNarrowProjection %}{% set inputActivation = inputActivation | default("none") %}
|
| 59 |
+
{% set outputActivation = outputActivation | default("none") %}
|
| 60 |
+
{% if inputActivation == "relu" or outputActivation == "relu" %}
|
| 61 |
+
fn is_nan_f32(value: f32) -> bool {
|
| 62 |
+
let bits = bitcast<u32>(value);
|
| 63 |
+
return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
fn projection_relu(value: f32) -> f32 {
|
| 67 |
+
var out = max(value, 0.0);
|
| 68 |
+
if (is_nan_f32(value)) {
|
| 69 |
+
out = value;
|
| 70 |
+
}
|
| 71 |
+
return out;
|
| 72 |
+
}
|
| 73 |
+
{% endif %}
|
| 74 |
+
{% if outputActivation == "sigmoid" %}
|
| 75 |
+
fn sigmoid_safe(x: f32) -> f32 {
|
| 76 |
+
if (x >= 0.0) {
|
| 77 |
+
let z = exp(-x);
|
| 78 |
+
return 1.0 / (1.0 + z);
|
| 79 |
+
}
|
| 80 |
+
let z = exp(x);
|
| 81 |
+
return z / (1.0 + z);
|
| 82 |
+
}
|
| 83 |
+
{% endif %}
|
| 84 |
+
{% endif %}
|
| 85 |
|
| 86 |
{% set operandScalar = fScalar %}
|
| 87 |
{% set accScalar = "f32" %}
|
|
|
|
| 98 |
|
| 99 |
const M: u32 = {{ M }}u;
|
| 100 |
const K: u32 = {{ K }}u;
|
| 101 |
+
{% if not (splitKPartial and padded) or implicit %}
|
| 102 |
const N: u32 = {{ N }}u;
|
| 103 |
+
{% endif %}
|
| 104 |
{% if padded %}
|
| 105 |
const K_LOOP: u32 = {{ kPadded }}u;
|
| 106 |
{% endif %}
|
| 107 |
+
{% if implicit %}
|
| 108 |
+
const CONV_KERNEL_H: u32 = {{ convKernelH }}u;
|
| 109 |
+
const CONV_KERNEL_W: u32 = {{ convKernelW }}u;
|
| 110 |
+
{% if implicit3d %}
|
| 111 |
+
const CONV_KERNEL_D: u32 = {{ convKernelD }}u;
|
| 112 |
+
const CONV_KSIZE_HW: u32 = CONV_KERNEL_H * CONV_KERNEL_W;
|
| 113 |
+
const CONV_KSIZE: u32 = CONV_KERNEL_D * CONV_KSIZE_HW;
|
| 114 |
+
const CONV_STRIDE_D: u32 = {{ convStrideD }}u;
|
| 115 |
+
const CONV_DILATION_D: u32 = {{ convDilationD }}u;
|
| 116 |
+
const CONV_PAD_FRONT: i32 = {{ convPadFront }};
|
| 117 |
+
const CONV_IN_D: i32 = {{ convInD }};
|
| 118 |
+
const CONV_OUT_H: u32 = {{ convOutH }}u;
|
| 119 |
+
{% else %}
|
| 120 |
+
const CONV_KSIZE: u32 = CONV_KERNEL_H * CONV_KERNEL_W;
|
| 121 |
+
{% endif %}
|
| 122 |
+
const CONV_STRIDE_H: u32 = {{ convStrideH }}u;
|
| 123 |
+
const CONV_STRIDE_W: u32 = {{ convStrideW }}u;
|
| 124 |
+
const CONV_DILATION_H: u32 = {{ convDilationH }}u;
|
| 125 |
+
const CONV_DILATION_W: u32 = {{ convDilationW }}u;
|
| 126 |
+
const CONV_PAD_TOP: i32 = {{ convPadTop }};
|
| 127 |
+
const CONV_PAD_LEFT: i32 = {{ convPadLeft }};
|
| 128 |
+
const CONV_IN_H: i32 = {{ convInH }};
|
| 129 |
+
const CONV_IN_W: i32 = {{ convInW }};
|
| 130 |
+
const CONV_OUT_W: u32 = {{ convOutW }}u;
|
| 131 |
+
{% if implicit3d %}
|
| 132 |
+
// X is [batch, inChannels, inD, inH, inW]; the tile gather indexes it directly.
|
| 133 |
+
const B_BATCH_STRIDE: u32 = {{ convInChannels }}u * u32(CONV_IN_D) * u32(CONV_IN_H) * u32(CONV_IN_W);
|
| 134 |
+
{% else %}
|
| 135 |
+
// X is [batch, inChannels, inH, inW]; the tile gather indexes it directly.
|
| 136 |
+
const B_BATCH_STRIDE: u32 = {{ convInChannels }}u * u32(CONV_IN_H) * u32(CONV_IN_W);
|
| 137 |
+
{% endif %}
|
| 138 |
+
{% else %}
|
| 139 |
{% if padded %}
|
| 140 |
const N_COLS: u32 = {{ nPadded }}u;
|
| 141 |
{% endif %}
|
| 142 |
const B_BATCH_STRIDE: u32 = {{ kLoopVar }} * {{ nColsVar }};
|
| 143 |
+
{% endif %}
|
| 144 |
{% if not splitKPartial and not polyphase and not fusedNarrowProjection %}
|
| 145 |
const C_BATCH_STRIDE: u32 = M * N;
|
| 146 |
{% endif %}
|
| 147 |
+
{% if splitKPartial %}
|
| 148 |
+
const SPLIT_K: u32 = {{ splitKValue }}u;
|
| 149 |
+
// Whole 32-wide K tiles per slice, rounded up, with the last slice clamped to
|
| 150 |
+
// K_LOOP. This avoids padding K to a multiple of SPLIT_K times the tile width.
|
| 151 |
+
// A slice entirely past K_LOOP runs zero iterations and stores zero
|
| 152 |
+
// accumulators, which is the correct partial for an empty K range.
|
| 153 |
+
const K_CHUNK: u32 = {{ kChunk }}u;
|
| 154 |
+
const PARTIAL_ROWS: u32 = {{ mPadded }}u;
|
| 155 |
+
const PARTIAL_COLS: u32 = {{ nPadded }}u;
|
| 156 |
+
const PARTIAL_SLICE_STRIDE: u32 = {{ batchCount }}u * PARTIAL_ROWS * PARTIAL_COLS;
|
| 157 |
+
{% endif %}
|
| 158 |
const TILE_COLS: u32 = {{ tileColsValue }}u;
|
| 159 |
const TILE_ROWS: u32 = {{ tileRowsValue }}u;
|
| 160 |
const TILE_K: u32 = 32u;
|
| 161 |
const SUB_COLS: u32 = {{ subColsValue }}u;
|
| 162 |
const SUB_ROWS: u32 = {{ subRowsValue }}u;
|
| 163 |
|
| 164 |
+
{% if not directMatrixInputs %}
|
| 165 |
var<workgroup> tile_A: array<{{ operandScalar }}, {{ tileRowsValue }} * 32>;
|
| 166 |
var<workgroup> tile_B: array<{{ operandScalar }}, {{ tileColsValue }} * 32>;
|
| 167 |
+
{% endif %}
|
| 168 |
{% if (not useDirectMatrixStore or padded) and not splitKPartial and not fusedNarrowProjection %}
|
| 169 |
var<workgroup> scratch: array<array<array<{{ accScalar }}, 64>, 4>, {{ (workgroupThreadsValue / 32)|int }}>;
|
| 170 |
|
| 171 |
{% endif %}
|
| 172 |
+
{% if not directMatrixInputs %}
|
| 173 |
fn loadSHMA(tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
|
| 174 |
let a_global = tile_base + row;
|
| 175 |
let col = c_idx * 8u;
|
|
|
|
| 195 |
fn loadSHMB(b_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
|
| 196 |
let b_col = tile_base + row;
|
| 197 |
let col = c_idx * {{ bLoadWidth }}u;
|
| 198 |
+
{% if implicit %}
|
| 199 |
+
// One output position per tile column, so its window origin is loop-invariant.
|
| 200 |
+
{% if implicit3d %}
|
| 201 |
+
let id0 = i32((b_col / (CONV_OUT_W * CONV_OUT_H)) * CONV_STRIDE_D) - CONV_PAD_FRONT;
|
| 202 |
+
let ih0 = i32(((b_col / CONV_OUT_W) % CONV_OUT_H) * CONV_STRIDE_H) - CONV_PAD_TOP;
|
| 203 |
+
{% else %}
|
| 204 |
+
let ih0 = i32((b_col / CONV_OUT_W) * CONV_STRIDE_H) - CONV_PAD_TOP;
|
| 205 |
+
{% endif %}
|
| 206 |
+
let iw0 = i32((b_col % CONV_OUT_W) * CONV_STRIDE_W) - CONV_PAD_LEFT;
|
| 207 |
+
let in_column = b_col < N;
|
| 208 |
+
// k advances by exactly one per element, so the (in-channel, tap-row, tap-col)
|
| 209 |
+
// decomposition is CARRIED across the loop instead of recomputed: two divisions
|
| 210 |
+
// and two moduli per loaded element become one increment and one compare, with
|
| 211 |
+
// the source coordinates advanced by the dilation in step. Only the entry values
|
| 212 |
+
// divide, and their divisors are compile-time literals.
|
| 213 |
+
var k = k_idx + col;
|
| 214 |
+
var ic = k / CONV_KSIZE;
|
| 215 |
+
var kq = k % CONV_KSIZE;
|
| 216 |
+
{% if implicit3d %}
|
| 217 |
+
var kd = kq / CONV_KSIZE_HW;
|
| 218 |
+
var khw = kq % CONV_KSIZE_HW;
|
| 219 |
+
var kh = khw / CONV_KERNEL_W;
|
| 220 |
+
var kw = khw % CONV_KERNEL_W;
|
| 221 |
+
var id = id0 + i32(kd * CONV_DILATION_D);
|
| 222 |
+
{% else %}
|
| 223 |
+
var kh = kq / CONV_KERNEL_W;
|
| 224 |
+
var kw = kq % CONV_KERNEL_W;
|
| 225 |
+
{% endif %}
|
| 226 |
+
var ih = ih0 + i32(kh * CONV_DILATION_H);
|
| 227 |
+
var iw = iw0 + i32(kw * CONV_DILATION_W);
|
| 228 |
+
// A tile column whose whole kernel window and K span are in bounds cannot
|
| 229 |
+
// take any of the element guards below. Hoist them into one uniform test so
|
| 230 |
+
// the interior loop carries a plain address instead of recomputing coordinates.
|
| 231 |
+
let interior = in_column
|
| 232 |
+
&& k_idx + col + {{ bLoadWidth }}u <= K
|
| 233 |
+
{% if implicit3d %}
|
| 234 |
+
&& id0 >= 0 && id0 + i32((CONV_KERNEL_D - 1u) * CONV_DILATION_D) < CONV_IN_D
|
| 235 |
+
{% endif %}
|
| 236 |
+
&& ih0 >= 0 && ih0 + i32((CONV_KERNEL_H - 1u) * CONV_DILATION_H) < CONV_IN_H
|
| 237 |
+
&& iw0 >= 0 && iw0 + i32((CONV_KERNEL_W - 1u) * CONV_DILATION_W) < CONV_IN_W;
|
| 238 |
+
if (interior) {
|
| 239 |
+
let colStep = i32(CONV_DILATION_W);
|
| 240 |
+
let rowStep = i32(CONV_DILATION_H) * CONV_IN_W;
|
| 241 |
+
{% if implicit3d %}
|
| 242 |
+
let depthStep = i32(CONV_DILATION_D) * CONV_IN_H * CONV_IN_W;
|
| 243 |
+
let planeStep = CONV_IN_D * CONV_IN_H * CONV_IN_W;
|
| 244 |
+
{% else %}
|
| 245 |
+
let planeStep = CONV_IN_H * CONV_IN_W;
|
| 246 |
+
{% endif %}
|
| 247 |
+
// Advancing k already added one colStep, so each wrap rewinds that plus the taps it walked.
|
| 248 |
+
let kwWrap = colStep * i32(CONV_KERNEL_W) - rowStep;
|
| 249 |
+
{% if implicit3d %}
|
| 250 |
+
let khWrap = rowStep * i32(CONV_KERNEL_H) - depthStep;
|
| 251 |
+
let kdWrap = depthStep * i32(CONV_KERNEL_D) - planeStep;
|
| 252 |
+
var addr = i32(b_base) + i32(ic) * planeStep + (id * CONV_IN_H + ih) * CONV_IN_W + iw;
|
| 253 |
+
{% else %}
|
| 254 |
+
let khWrap = rowStep * i32(CONV_KERNEL_H) - planeStep;
|
| 255 |
+
var addr = i32(b_base) + i32(ic) * planeStep + ih * CONV_IN_W + iw;
|
| 256 |
+
{% endif %}
|
| 257 |
+
for (var i = 0u; i < {{ bLoadWidth }}u; i = i + 1u) {
|
| 258 |
+
tile_B[row * TILE_K + col + i] = {{ operandScalar }}(xm[u32(addr)]);
|
| 259 |
+
addr = addr + colStep;
|
| 260 |
+
kw = kw + 1u;
|
| 261 |
+
if (kw == CONV_KERNEL_W) {
|
| 262 |
+
kw = 0u;
|
| 263 |
+
addr = addr - kwWrap;
|
| 264 |
+
kh = kh + 1u;
|
| 265 |
+
if (kh == CONV_KERNEL_H) {
|
| 266 |
+
kh = 0u;
|
| 267 |
+
addr = addr - khWrap;
|
| 268 |
+
{% if implicit3d %}
|
| 269 |
+
kd = kd + 1u;
|
| 270 |
+
if (kd == CONV_KERNEL_D) {
|
| 271 |
+
kd = 0u;
|
| 272 |
+
addr = addr - kdWrap;
|
| 273 |
+
}
|
| 274 |
+
{% endif %}
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
}
|
| 278 |
+
return;
|
| 279 |
+
}
|
| 280 |
+
for (var i = 0u; i < {{ bLoadWidth }}u; i = i + 1u) {
|
| 281 |
+
var value = {{ operandScalar }}(0.0);
|
| 282 |
+
// The K and N tails read as exact zeros, exactly as the materialized padded
|
| 283 |
+
// cols buffer does, so they contribute nothing to the dot product.
|
| 284 |
+
if (in_column && k < K{% if implicit3d %} && id >= 0 && id < CONV_IN_D{% endif %} && ih >= 0 && ih < CONV_IN_H && iw >= 0 && iw < CONV_IN_W) {
|
| 285 |
+
{% if implicit3d %}
|
| 286 |
+
value = {{ operandScalar }}(xm[b_base + ((ic * u32(CONV_IN_D) + u32(id)) * u32(CONV_IN_H) + u32(ih)) * u32(CONV_IN_W) + u32(iw)]);
|
| 287 |
+
{% else %}
|
| 288 |
+
value = {{ operandScalar }}(xm[b_base + (ic * u32(CONV_IN_H) + u32(ih)) * u32(CONV_IN_W) + u32(iw)]);
|
| 289 |
+
{% endif %}
|
| 290 |
+
}
|
| 291 |
+
tile_B[row * TILE_K + col + i] = value;
|
| 292 |
+
k = k + 1u;
|
| 293 |
+
kw = kw + 1u;
|
| 294 |
+
iw = iw + i32(CONV_DILATION_W);
|
| 295 |
+
if (kw == CONV_KERNEL_W) {
|
| 296 |
+
kw = 0u;
|
| 297 |
+
iw = iw0;
|
| 298 |
+
kh = kh + 1u;
|
| 299 |
+
ih = ih + i32(CONV_DILATION_H);
|
| 300 |
+
if (kh == CONV_KERNEL_H) {
|
| 301 |
+
kh = 0u;
|
| 302 |
+
ih = ih0;
|
| 303 |
+
{% if implicit3d %}
|
| 304 |
+
kd = kd + 1u;
|
| 305 |
+
id = id + i32(CONV_DILATION_D);
|
| 306 |
+
if (kd == CONV_KERNEL_D) {
|
| 307 |
+
kd = 0u;
|
| 308 |
+
id = id0;
|
| 309 |
+
ic = ic + 1u;
|
| 310 |
+
}
|
| 311 |
+
{% else %}
|
| 312 |
+
ic = ic + 1u;
|
| 313 |
+
{% endif %}
|
| 314 |
+
}
|
| 315 |
+
}
|
| 316 |
+
}
|
| 317 |
+
{% else %}
|
| 318 |
for (var i = 0u; i < {{ bLoadWidth }}u; i = i + 1u) {
|
| 319 |
let k = k_idx + col + i;
|
| 320 |
{% if operandScalar == "f16" %}
|
|
|
|
| 323 |
tile_B[row * TILE_K + col + i] = f32(xm[b_base + k * {{ nColsVar }} + b_col]);
|
| 324 |
{% endif %}
|
| 325 |
}
|
| 326 |
+
{% endif %}
|
| 327 |
}
|
| 328 |
|
| 329 |
+
{% endif %}
|
| 330 |
{% set hasActivation = hasActivation is defined and hasActivation %}
|
| 331 |
+
{% set activation = activation | default("") %}
|
| 332 |
+
{% set actAlpha = actAlpha | default(0.0) %}
|
| 333 |
+
{% set actBeta = actBeta | default(0.0) %}
|
| 334 |
{% if hasActivation %}
|
| 335 |
// Apply the fused activation in the f32 accumulator before the single output
|
| 336 |
// cast, avoiding an intermediate convolution tensor.
|
| 337 |
+
{% macro fused_act_return(mode, alpha, beta) -%}
|
| 338 |
+
{% if mode == "Relu" %}
|
| 339 |
return max(v, 0.0);
|
| 340 |
+
{% elif mode == "Clip" %}
|
| 341 |
+
return clamp(v, f32({{ alpha }}), f32({{ beta }}));
|
| 342 |
+
{% elif mode == "LeakyRelu" %}
|
| 343 |
+
return select(v * f32({{ alpha }}), v, v >= 0.0);
|
| 344 |
+
{% elif mode == "Sigmoid" %}
|
| 345 |
return 1.0 / (1.0 + exp(-v));
|
| 346 |
+
{% elif mode == "Tanh" %}
|
| 347 |
+
// tanh is already +/-1 to f32 precision at the clamp bounds. Clamping before
|
| 348 |
+
// the builtin preserves that saturated result for larger accumulators.
|
|
|
|
| 349 |
return tanh(clamp(v, -10.0, 10.0));
|
| 350 |
+
{% elif mode == "HardSigmoid" %}
|
| 351 |
+
return clamp(f32({{ alpha }}) * v + f32({{ beta }}), 0.0, 1.0);
|
| 352 |
{% else %}
|
| 353 |
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 354 |
{% endif %}
|
| 355 |
+
{%- endmacro -%}
|
| 356 |
+
fn fused_act(v: f32) -> f32 {
|
| 357 |
+
{{ fused_act_return(activation, actAlpha, actBeta) -}}
|
| 358 |
}
|
| 359 |
{% endif %}
|
| 360 |
|
|
|
|
| 422 |
{% endif %}
|
| 423 |
@builtin(subgroup_size) sg_size: u32
|
| 424 |
) {
|
| 425 |
+
{% if splitKPartial %}
|
| 426 |
+
// z carries both axes, with split slices contiguous inside each batch image.
|
| 427 |
+
let slice = workgroup_id.z % SPLIT_K;
|
| 428 |
+
let batch = workgroup_id.z / SPLIT_K;
|
| 429 |
+
let b_base = batch * B_BATCH_STRIDE;
|
| 430 |
+
let c_base = slice * PARTIAL_SLICE_STRIDE + batch * PARTIAL_ROWS * PARTIAL_COLS;
|
| 431 |
+
{% else %}
|
| 432 |
let batch = workgroup_id.z;
|
| 433 |
let b_base = batch * B_BATCH_STRIDE;
|
| 434 |
{% if not fusedNarrowProjection %}
|
| 435 |
let c_base = batch * C_BATCH_STRIDE;
|
| 436 |
+
{% endif %}
|
| 437 |
{% endif %}
|
| 438 |
let a_global_base = workgroup_id.y * TILE_ROWS;
|
| 439 |
let b_global_base = workgroup_id.x * TILE_COLS;
|
|
|
|
| 452 |
var matC12: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
|
| 453 |
var matC13: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
|
| 454 |
|
| 455 |
+
{% if splitKPartial %}
|
| 456 |
+
let k_begin = slice * K_CHUNK;
|
| 457 |
+
let k_end = min(k_begin + K_CHUNK, {{ kLoopVar }});
|
| 458 |
+
for (var kidx = k_begin; kidx < k_end; kidx = kidx + TILE_K) {
|
| 459 |
+
{% else %}
|
| 460 |
for (var kidx = 0u; kidx < {{ kLoopVar }}; kidx = kidx + TILE_K) {
|
| 461 |
+
{% endif %}
|
| 462 |
+
{% if not directMatrixInputs %}
|
| 463 |
loadSHMA(a_global_base, kidx, local_idx / 4u, local_idx % 4u);
|
| 464 |
loadSHMB(b_base, b_global_base, kidx, local_idx / {{ bKChunks }}u, local_idx % {{ bKChunks }}u);
|
| 465 |
workgroupBarrier();
|
| 466 |
+
{% endif %}
|
| 467 |
|
| 468 |
for (var step = 0u; step < TILE_K; step = step + 8u) {
|
| 469 |
+
{% set directInputs = directMatrixInputs is defined and directMatrixInputs %}
|
| 470 |
+
let matrix_a_offset = {% if directInputs %}(a_global_base + subtile_idy * SUB_ROWS) * K + kidx + step{% else %}subtile_idy * SUB_ROWS * TILE_K + step{% endif %};
|
| 471 |
+
{% for r in range(2) %}
|
| 472 |
+
var matA{{ r }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&{{ "w" if directInputs else "tile_A" }}, matrix_a_offset{% if r > 0 %} + 8u * {{ "K" if directInputs else "TILE_K" }}{% endif %}, {{ "K" if directInputs else "TILE_K" }});
|
| 473 |
+
{% endfor %}
|
| 474 |
|
| 475 |
+
let matrix_b_offset = {% if directInputs %}b_base + (kidx + step) * N + b_global_base + subtile_idx * SUB_COLS{% else %}subtile_idx * SUB_COLS * TILE_K + step{% endif %};
|
| 476 |
+
{% for c in range(4) %}
|
| 477 |
+
var matB{{ c }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, {% if directInputs %}row_major{% else %}col_major{% endif %}>(&{{ "xm" if directInputs else "tile_B" }}, matrix_b_offset{% if c > 0 %} + {{ c * 8 }}u{% if not directInputs %} * TILE_K{% endif %}{% endif %}, {{ "N" if directInputs else "TILE_K" }});
|
| 478 |
+
{% endfor %}
|
|
|
|
| 479 |
|
| 480 |
matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
|
| 481 |
matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
|
|
|
|
| 486 |
matC12 = subgroupMatrixMultiplyAccumulate(matA1, matB2, matC12);
|
| 487 |
matC13 = subgroupMatrixMultiplyAccumulate(matA1, matB3, matC13);
|
| 488 |
}
|
| 489 |
+
{% if not directMatrixInputs %}
|
| 490 |
workgroupBarrier();
|
| 491 |
+
{% endif %}
|
| 492 |
}
|
| 493 |
|
| 494 |
{% if fusedNarrowProjection %}
|
|
|
|
| 518 |
{% endfor %}
|
| 519 |
for (var channel = 0u; channel < M; channel = channel + 1u) {
|
| 520 |
let producer_value = tile_B[channel * TILE_COLS + local_idx]{% if hasBias %} + f32(bias[channel]){% endif %};
|
| 521 |
+
{% if inputActivation == "relu" %}
|
| 522 |
+
let activated_value = projection_relu(producer_value);
|
| 523 |
+
{% else %}
|
| 524 |
let activated_value = producer_value;
|
| 525 |
+
{% endif %}
|
| 526 |
{% for oc in range(projectionOutChannels) %}
|
| 527 |
projected_acc{{ oc }} = projected_acc{{ oc }} + activated_value * f32(projectionW[{{ oc }}u * M + channel]);
|
| 528 |
{% endfor %}
|
|
|
|
| 530 |
|
| 531 |
let fused_y_base = batch * {{ projectionOutChannels }}u * N + fused_global_col;
|
| 532 |
{% for oc in range(projectionOutChannels) %}
|
| 533 |
+
let projected{{ oc }} = projected_acc{{ oc }}{% if hasProjectionBias %} + f32(projectionBias[{{ oc }}u]){% endif %};
|
| 534 |
+
{% if outputActivation == "relu" %}
|
| 535 |
+
let activated{{ oc }} = projection_relu(projected{{ oc }});
|
| 536 |
+
{% elif outputActivation == "sigmoid" %}
|
| 537 |
+
let activated{{ oc }} = sigmoid_safe(projected{{ oc }});
|
| 538 |
+
{% else %}
|
| 539 |
let activated{{ oc }} = projected{{ oc }};
|
| 540 |
+
{% endif %}
|
| 541 |
+
y[fused_y_base + {{ oc }}u * N] = activated{{ oc }}{% if hasOutputScale %} * params.outputScale{% endif %};
|
| 542 |
{% endfor %}
|
| 543 |
}
|
| 544 |
+
{% elif splitKPartial %}
|
| 545 |
+
// Raw partials, no bias and no epilogue — the reduce pass owns both. The
|
| 546 |
+
// scratch is whole tiles in both axes, so this needs none of the column or
|
| 547 |
+
// row guards the real output store carries.
|
| 548 |
+
let partial_offset = c_base + (a_global_base + base_A) * PARTIAL_COLS + b_global_base + base_B;
|
| 549 |
+
subgroupMatrixStore<row_major>(&y, partial_offset + 0u * PARTIAL_COLS + 0u, matC00, PARTIAL_COLS);
|
| 550 |
+
subgroupMatrixStore<row_major>(&y, partial_offset + 0u * PARTIAL_COLS + 8u, matC01, PARTIAL_COLS);
|
| 551 |
+
subgroupMatrixStore<row_major>(&y, partial_offset + 0u * PARTIAL_COLS + 16u, matC02, PARTIAL_COLS);
|
| 552 |
+
subgroupMatrixStore<row_major>(&y, partial_offset + 0u * PARTIAL_COLS + 24u, matC03, PARTIAL_COLS);
|
| 553 |
+
subgroupMatrixStore<row_major>(&y, partial_offset + 8u * PARTIAL_COLS + 0u, matC10, PARTIAL_COLS);
|
| 554 |
+
subgroupMatrixStore<row_major>(&y, partial_offset + 8u * PARTIAL_COLS + 8u, matC11, PARTIAL_COLS);
|
| 555 |
+
subgroupMatrixStore<row_major>(&y, partial_offset + 8u * PARTIAL_COLS + 16u, matC12, PARTIAL_COLS);
|
| 556 |
+
subgroupMatrixStore<row_major>(&y, partial_offset + 8u * PARTIAL_COLS + 24u, matC13, PARTIAL_COLS);
|
| 557 |
{% elif useDirectMatrixStore %}
|
| 558 |
{% if padded %}
|
| 559 |
// Padded N only needs guarded publication for the final partial-width tile.
|
build/webgpu/conv-direct-nd.wgsl.jinja
CHANGED
|
@@ -1,42 +1,47 @@
|
|
| 1 |
-
// Direct
|
| 2 |
// accumulated in f32 and narrowed once at the output.
|
| 3 |
// A fused epilogue may apply a residual input and activation before the store.
|
| 4 |
-
{% if
|
| 5 |
enable f16;
|
| 6 |
{% endif %}
|
| 7 |
{{ env.wgsl.resourceDeclarations }}
|
| 8 |
{% set hasActivation = hasActivation is defined and hasActivation %}
|
|
|
|
|
|
|
|
|
|
| 9 |
{% if hasActivation %}
|
| 10 |
// Apply the fused activation in the f32 accumulator before the single output
|
| 11 |
// cast, avoiding an intermediate convolution tensor.
|
| 12 |
-
|
| 13 |
-
{% if
|
| 14 |
return max(v, 0.0);
|
| 15 |
-
{% elif
|
| 16 |
-
return clamp(v, f32({{
|
| 17 |
-
{% elif
|
| 18 |
-
return select(v * f32({{
|
| 19 |
-
{% elif
|
| 20 |
return 1.0 / (1.0 + exp(-v));
|
| 21 |
-
{% elif
|
| 22 |
-
//
|
| 23 |
-
//
|
| 24 |
-
// already +/-1 to full f32 precision by |v| ~ 9.
|
| 25 |
return tanh(clamp(v, -10.0, 10.0));
|
| 26 |
-
{% elif
|
| 27 |
-
return clamp(f32({{
|
| 28 |
{% else %}
|
| 29 |
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 30 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
| 31 |
}
|
| 32 |
{% endif %}
|
| 33 |
|
|
|
|
| 34 |
const WG: u32 = {{ convWorkgroupSize }}u;
|
| 35 |
|
| 36 |
@compute @workgroup_size(WG)
|
| 37 |
-
fn main(@builtin(global_invocation_id) gid: vec3<u32>
|
| 38 |
-
|
| 39 |
-
let index = gid.x + gid.y * nwg.x * WG;
|
| 40 |
if (index >= params.count) {
|
| 41 |
return;
|
| 42 |
}
|
|
@@ -52,7 +57,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
|
|
| 52 |
let group = oc / params.outChannelsPerGroup;
|
| 53 |
|
| 54 |
var acc = 0.0;
|
| 55 |
-
{% if
|
| 56 |
acc = f32(bias[oc]);
|
| 57 |
{% endif %}
|
| 58 |
for (var ic = 0u; ic < params.weightInChannels; ic += 1u) {
|
|
|
|
| 1 |
+
// Direct N-dimensional convolution for channels-first tensors. Inputs are
|
| 2 |
// accumulated in f32 and narrowed once at the output.
|
| 3 |
// A fused epilogue may apply a residual input and activation before the store.
|
| 4 |
+
{% if usesF16Spec %}
|
| 5 |
enable f16;
|
| 6 |
{% endif %}
|
| 7 |
{{ env.wgsl.resourceDeclarations }}
|
| 8 |
{% set hasActivation = hasActivation is defined and hasActivation %}
|
| 9 |
+
{% set activation = activation | default("") %}
|
| 10 |
+
{% set actAlpha = actAlpha | default(0.0) %}
|
| 11 |
+
{% set actBeta = actBeta | default(0.0) %}
|
| 12 |
{% if hasActivation %}
|
| 13 |
// Apply the fused activation in the f32 accumulator before the single output
|
| 14 |
// cast, avoiding an intermediate convolution tensor.
|
| 15 |
+
{% macro fused_act_return(mode, alpha, beta) -%}
|
| 16 |
+
{% if mode == "Relu" %}
|
| 17 |
return max(v, 0.0);
|
| 18 |
+
{% elif mode == "Clip" %}
|
| 19 |
+
return clamp(v, f32({{ alpha }}), f32({{ beta }}));
|
| 20 |
+
{% elif mode == "LeakyRelu" %}
|
| 21 |
+
return select(v * f32({{ alpha }}), v, v >= 0.0);
|
| 22 |
+
{% elif mode == "Sigmoid" %}
|
| 23 |
return 1.0 / (1.0 + exp(-v));
|
| 24 |
+
{% elif mode == "Tanh" %}
|
| 25 |
+
// tanh is already +/-1 to f32 precision at the clamp bounds. Clamping before
|
| 26 |
+
// the builtin preserves that saturated result for larger accumulators.
|
|
|
|
| 27 |
return tanh(clamp(v, -10.0, 10.0));
|
| 28 |
+
{% elif mode == "HardSigmoid" %}
|
| 29 |
+
return clamp(f32({{ alpha }}) * v + f32({{ beta }}), 0.0, 1.0);
|
| 30 |
{% else %}
|
| 31 |
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 32 |
{% endif %}
|
| 33 |
+
{%- endmacro -%}
|
| 34 |
+
fn fused_act(v: f32) -> f32 {
|
| 35 |
+
{{ fused_act_return(activation, actAlpha, actBeta) -}}
|
| 36 |
}
|
| 37 |
{% endif %}
|
| 38 |
|
| 39 |
+
{% set hasZ = hasZ is defined and hasZ %}
|
| 40 |
const WG: u32 = {{ convWorkgroupSize }}u;
|
| 41 |
|
| 42 |
@compute @workgroup_size(WG)
|
| 43 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 44 |
+
let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
|
|
|
|
| 45 |
if (index >= params.count) {
|
| 46 |
return;
|
| 47 |
}
|
|
|
|
| 57 |
let group = oc / params.outChannelsPerGroup;
|
| 58 |
|
| 59 |
var acc = 0.0;
|
| 60 |
+
{% if hasBias %}
|
| 61 |
acc = f32(bias[oc]);
|
| 62 |
{% endif %}
|
| 63 |
for (var ic = 0u; ic < params.weightInChannels; ic += 1u) {
|
build/webgpu/conv-direct-unrolled.wgsl.jinja
CHANGED
|
@@ -1,53 +1,55 @@
|
|
| 1 |
// Direct NCHW 2D convolution with kernel extent, strides, dilations, and
|
| 2 |
// top/left pads compiled into the fully unrolled window below.
|
| 3 |
-
// One thread
|
| 4 |
-
//
|
| 5 |
-
// scalar NCHW fallback, so f32 results are bit-identical to it.
|
| 6 |
// The kh/kw window offsets, kh*KW+kw weight offsets, and the per-channel
|
| 7 |
// strides fold to literals; the per-row bounds check is hoisted per kh.
|
| 8 |
-
//
|
| 9 |
-
//
|
| 10 |
-
{% if usesF16 %}
|
| 11 |
-
enable f16;
|
| 12 |
-
{% endif %}
|
| 13 |
{{ env.wgsl.resourceDeclarations }}
|
| 14 |
{% set hasActivation = hasActivation is defined and hasActivation %}
|
|
|
|
|
|
|
|
|
|
| 15 |
{% if hasActivation %}
|
| 16 |
// Apply the fused activation in the f32 accumulator before the single output
|
| 17 |
// cast, avoiding an intermediate convolution tensor.
|
| 18 |
-
|
| 19 |
-
{% if
|
| 20 |
return max(v, 0.0);
|
| 21 |
-
{% elif
|
| 22 |
-
return clamp(v, f32({{
|
| 23 |
-
{% elif
|
| 24 |
-
return select(v * f32({{
|
| 25 |
-
{% elif
|
| 26 |
return 1.0 / (1.0 + exp(-v));
|
| 27 |
-
{% elif
|
| 28 |
-
//
|
| 29 |
-
//
|
| 30 |
-
// already +/-1 to full f32 precision by |v| ~ 9.
|
| 31 |
return tanh(clamp(v, -10.0, 10.0));
|
| 32 |
-
{% elif
|
| 33 |
-
return clamp(f32({{
|
| 34 |
{% else %}
|
| 35 |
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 36 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
| 37 |
}
|
| 38 |
{% endif %}
|
| 39 |
|
| 40 |
-
|
| 41 |
-
const
|
| 42 |
-
const
|
| 43 |
-
const
|
| 44 |
-
const
|
|
|
|
| 45 |
|
| 46 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 47 |
-
fn main(@builtin(global_invocation_id) gid: vec3<u32>
|
| 48 |
// 2D-folded flat index: gid.y carries the high bits past the
|
| 49 |
-
//
|
| 50 |
-
let index = gid.x + gid.y *
|
| 51 |
if (index >= params.count) {
|
| 52 |
return;
|
| 53 |
}
|
|
@@ -71,16 +73,16 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
|
|
| 71 |
acc = f32(bias[oc]);
|
| 72 |
{% endif %}
|
| 73 |
for (var ic = 0u; ic < params.weightInChannels; ic = ic + 1u) {
|
| 74 |
-
{% for kh in range(
|
| 75 |
{
|
| 76 |
-
let ih = ihBase + {{ kh *
|
| 77 |
if (ih >= 0 && ih < i32(params.inH)) {
|
| 78 |
let xRow = xBase + u32(ih) * params.inW;
|
| 79 |
-
{% for kw in range(
|
| 80 |
{
|
| 81 |
-
let iw = iwBase + {{ kw *
|
| 82 |
if (iw >= 0 && iw < i32(params.inW)) {
|
| 83 |
-
acc = acc + f32(x[xRow + u32(iw)]) * f32(w[wBase + {{ kh *
|
| 84 |
}
|
| 85 |
}
|
| 86 |
{% endfor %}
|
|
|
|
| 1 |
// Direct NCHW 2D convolution with kernel extent, strides, dilations, and
|
| 2 |
// top/left pads compiled into the fully unrolled window below.
|
| 3 |
+
// One thread owns each output element and accumulates with input channels outer,
|
| 4 |
+
// then kernel rows and columns in ascending order.
|
|
|
|
| 5 |
// The kh/kw window offsets, kh*KW+kw weight offsets, and the per-channel
|
| 6 |
// strides fold to literals; the per-row bounds check is hoisted per kh.
|
| 7 |
+
// Group offsets are applied to both input channels and weights. f16 inputs are
|
| 8 |
+
// widened to an f32 accumulator and narrowed once at store.
|
|
|
|
|
|
|
|
|
|
| 9 |
{{ env.wgsl.resourceDeclarations }}
|
| 10 |
{% set hasActivation = hasActivation is defined and hasActivation %}
|
| 11 |
+
{% set activation = activation | default("") %}
|
| 12 |
+
{% set actAlpha = actAlpha | default(0.0) %}
|
| 13 |
+
{% set actBeta = actBeta | default(0.0) %}
|
| 14 |
{% if hasActivation %}
|
| 15 |
// Apply the fused activation in the f32 accumulator before the single output
|
| 16 |
// cast, avoiding an intermediate convolution tensor.
|
| 17 |
+
{% macro fused_act_return(mode, alpha, beta) -%}
|
| 18 |
+
{% if mode == "Relu" %}
|
| 19 |
return max(v, 0.0);
|
| 20 |
+
{% elif mode == "Clip" %}
|
| 21 |
+
return clamp(v, f32({{ alpha }}), f32({{ beta }}));
|
| 22 |
+
{% elif mode == "LeakyRelu" %}
|
| 23 |
+
return select(v * f32({{ alpha }}), v, v >= 0.0);
|
| 24 |
+
{% elif mode == "Sigmoid" %}
|
| 25 |
return 1.0 / (1.0 + exp(-v));
|
| 26 |
+
{% elif mode == "Tanh" %}
|
| 27 |
+
// tanh is already +/-1 to f32 precision at the clamp bounds. Clamping before
|
| 28 |
+
// the builtin preserves that saturated result for larger accumulators.
|
|
|
|
| 29 |
return tanh(clamp(v, -10.0, 10.0));
|
| 30 |
+
{% elif mode == "HardSigmoid" %}
|
| 31 |
+
return clamp(f32({{ alpha }}) * v + f32({{ beta }}), 0.0, 1.0);
|
| 32 |
{% else %}
|
| 33 |
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 34 |
{% endif %}
|
| 35 |
+
{%- endmacro -%}
|
| 36 |
+
fn fused_act(v: f32) -> f32 {
|
| 37 |
+
{{ fused_act_return(activation, actAlpha, actBeta) -}}
|
| 38 |
}
|
| 39 |
{% endif %}
|
| 40 |
|
| 41 |
+
{% set hasZ = hasZ is defined and hasZ %}
|
| 42 |
+
const KERNEL_AREA: u32 = {{ kernelHSpec * kernelWSpec }}u;
|
| 43 |
+
const STRIDE_H: u32 = {{ strideHSpec }}u;
|
| 44 |
+
const STRIDE_W: u32 = {{ strideWSpec }}u;
|
| 45 |
+
const PAD_TOP: i32 = {{ padTopSpec }};
|
| 46 |
+
const PAD_LEFT: i32 = {{ padLeftSpec }};
|
| 47 |
|
| 48 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 49 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 50 |
// 2D-folded flat index: gid.y carries the high bits past the
|
| 51 |
+
// per-axis dispatch fold width (outputs > 16.7M elements).
|
| 52 |
+
let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
|
| 53 |
if (index >= params.count) {
|
| 54 |
return;
|
| 55 |
}
|
|
|
|
| 73 |
acc = f32(bias[oc]);
|
| 74 |
{% endif %}
|
| 75 |
for (var ic = 0u; ic < params.weightInChannels; ic = ic + 1u) {
|
| 76 |
+
{% for kh in range(kernelHSpec) %}
|
| 77 |
{
|
| 78 |
+
let ih = ihBase + {{ kh * dilationHSpec }}i;
|
| 79 |
if (ih >= 0 && ih < i32(params.inH)) {
|
| 80 |
let xRow = xBase + u32(ih) * params.inW;
|
| 81 |
+
{% for kw in range(kernelWSpec) %}
|
| 82 |
{
|
| 83 |
+
let iw = iwBase + {{ kw * dilationWSpec }}i;
|
| 84 |
if (iw >= 0 && iw < i32(params.inW)) {
|
| 85 |
+
acc = acc + f32(x[xRow + u32(iw)]) * f32(w[wBase + {{ kh * kernelWSpec + kw }}u]);
|
| 86 |
}
|
| 87 |
}
|
| 88 |
{% endfor %}
|
build/webgpu/conv-im2col-nchw.wgsl.jinja
CHANGED
|
@@ -6,20 +6,19 @@
|
|
| 6 |
// gid.x grid-strides over positions for coalesced reads and writes; gid.y fixes
|
| 7 |
// k, allowing its ic/kh/kw decomposition to remain loop-invariant. The f32
|
| 8 |
// scratch round-trips f16 inputs exactly when the GEMM narrows its tile loads.
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
{% endif %}
|
| 12 |
{{ env.wgsl.resourceDeclarations }}
|
| 13 |
|
| 14 |
-
const KERNEL_H: u32 = {{
|
| 15 |
-
const KERNEL_W: u32 = {{
|
| 16 |
const KSIZE: u32 = KERNEL_H * KERNEL_W;
|
| 17 |
-
const STRIDE_H: u32 = {{
|
| 18 |
-
const STRIDE_W: u32 = {{
|
| 19 |
-
const DILATION_H: u32 = {{
|
| 20 |
-
const DILATION_W: u32 = {{
|
| 21 |
-
const PAD_TOP: i32 = {{
|
| 22 |
-
const PAD_LEFT: i32 = {{
|
| 23 |
{% if padded %}
|
| 24 |
// Padded materialization for the subgroup-matrix GEMM: the cols buffer is
|
| 25 |
// K -> ceil(K/32)*32 rows by N -> ceil(N/64)*64 columns, with rows >= kRows and
|
|
@@ -64,10 +63,10 @@ fn main(
|
|
| 64 |
}
|
| 65 |
{% if padded %}
|
| 66 |
}
|
| 67 |
-
cols[(batch * K_PADDED + k) * COLS_STRIDE + pos] = value;
|
| 68 |
-
{% else %}
|
| 69 |
-
cols[(batch * params.kRows + k) * params.outCount + pos] = value;
|
| 70 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
| 71 |
pos = pos + step;
|
| 72 |
}
|
| 73 |
}
|
|
|
|
| 6 |
// gid.x grid-strides over positions for coalesced reads and writes; gid.y fixes
|
| 7 |
// k, allowing its ic/kh/kw decomposition to remain loop-invariant. The f32
|
| 8 |
// scratch round-trips f16 inputs exactly when the GEMM narrows its tile loads.
|
| 9 |
+
// A plan for f16 inputs may retain f16 columns to avoid that widening.
|
| 10 |
+
{% set colsScalar = colsScalar | default("f32") %}
|
|
|
|
| 11 |
{{ env.wgsl.resourceDeclarations }}
|
| 12 |
|
| 13 |
+
const KERNEL_H: u32 = {{ kernelHSpec }}u;
|
| 14 |
+
const KERNEL_W: u32 = {{ kernelWSpec }}u;
|
| 15 |
const KSIZE: u32 = KERNEL_H * KERNEL_W;
|
| 16 |
+
const STRIDE_H: u32 = {{ strideHSpec }}u;
|
| 17 |
+
const STRIDE_W: u32 = {{ strideWSpec }}u;
|
| 18 |
+
const DILATION_H: u32 = {{ dilationHSpec }}u;
|
| 19 |
+
const DILATION_W: u32 = {{ dilationWSpec }}u;
|
| 20 |
+
const PAD_TOP: i32 = {{ padTopSpec }};
|
| 21 |
+
const PAD_LEFT: i32 = {{ padLeftSpec }};
|
| 22 |
{% if padded %}
|
| 23 |
// Padded materialization for the subgroup-matrix GEMM: the cols buffer is
|
| 24 |
// K -> ceil(K/32)*32 rows by N -> ceil(N/64)*64 columns, with rows >= kRows and
|
|
|
|
| 63 |
}
|
| 64 |
{% if padded %}
|
| 65 |
}
|
|
|
|
|
|
|
|
|
|
| 66 |
{% endif %}
|
| 67 |
+
{% set columnRows = "K_PADDED" if padded else "params.kRows" %}
|
| 68 |
+
{% set columnStride = "COLS_STRIDE" if padded else "params.outCount" %}
|
| 69 |
+
cols[(batch * {{ columnRows }} + k) * {{ columnStride }} + pos] = {% if colsScalar == "f16" %}f16(value){% else %}value{% endif %};
|
| 70 |
pos = pos + step;
|
| 71 |
}
|
| 72 |
}
|
build/webgpu/conv-splitk-reduce.wgsl.jinja
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Pass 2 of the split-K Conv GEMM: sum the SPLIT_K raw partial slices that
|
| 2 |
+
// a Conv GEMM partial pass wrote, then apply the bias and epilogue
|
| 3 |
+
// that pass deliberately skipped, and publish the logical [batch, M, N] output.
|
| 4 |
+
//
|
| 5 |
+
// The partial scratch is [SPLIT_K, batch, PARTIAL_ROWS, PARTIAL_COLS] with both
|
| 6 |
+
// trailing axes padded to whole GEMM tiles; this pass indexes only the logical
|
| 7 |
+
// (batch, m, n), so the padded rows and columns are read by nobody and their
|
| 8 |
+
// contents never matter. One output element per thread, walking n fastest so
|
| 9 |
+
// consecutive threads touch consecutive addresses in every slice.
|
| 10 |
+
//
|
| 11 |
+
// Summing slices in index order changes the f32 association relative to one
|
| 12 |
+
// uninterrupted K loop, so the two orders need not be bit-identical.
|
| 13 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 14 |
+
{% set applyActivation = hasActivation is defined and hasActivation %}
|
| 15 |
+
{% if applyActivation %}
|
| 16 |
+
{% set hasActivation = hasActivation is defined and hasActivation %}
|
| 17 |
+
{% set activation = activation | default("") %}
|
| 18 |
+
{% set actAlpha = actAlpha | default(0.0) %}
|
| 19 |
+
{% set actBeta = actBeta | default(0.0) %}
|
| 20 |
+
{% if hasActivation %}
|
| 21 |
+
// Apply the fused activation in the f32 accumulator before the single output
|
| 22 |
+
// cast, avoiding an intermediate convolution tensor.
|
| 23 |
+
{% macro fused_act_return(mode, alpha, beta) -%}
|
| 24 |
+
{% if mode == "Relu" %}
|
| 25 |
+
return max(v, 0.0);
|
| 26 |
+
{% elif mode == "Clip" %}
|
| 27 |
+
return clamp(v, f32({{ alpha }}), f32({{ beta }}));
|
| 28 |
+
{% elif mode == "LeakyRelu" %}
|
| 29 |
+
return select(v * f32({{ alpha }}), v, v >= 0.0);
|
| 30 |
+
{% elif mode == "Sigmoid" %}
|
| 31 |
+
return 1.0 / (1.0 + exp(-v));
|
| 32 |
+
{% elif mode == "Tanh" %}
|
| 33 |
+
// tanh is already +/-1 to f32 precision at the clamp bounds. Clamping before
|
| 34 |
+
// the builtin preserves that saturated result for larger accumulators.
|
| 35 |
+
return tanh(clamp(v, -10.0, 10.0));
|
| 36 |
+
{% elif mode == "HardSigmoid" %}
|
| 37 |
+
return clamp(f32({{ alpha }}) * v + f32({{ beta }}), 0.0, 1.0);
|
| 38 |
+
{% else %}
|
| 39 |
+
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 40 |
+
{% endif %}
|
| 41 |
+
{%- endmacro -%}
|
| 42 |
+
fn fused_act(v: f32) -> f32 {
|
| 43 |
+
{{ fused_act_return(activation, actAlpha, actBeta) -}}
|
| 44 |
+
}
|
| 45 |
+
{% endif %}
|
| 46 |
+
|
| 47 |
+
{% endif %}
|
| 48 |
+
|
| 49 |
+
const M: u32 = {{ M }}u;
|
| 50 |
+
const N: u32 = {{ N }}u;
|
| 51 |
+
const SPLIT_K: u32 = {{ splitK }}u;
|
| 52 |
+
const PARTIAL_ROWS: u32 = {{ mPadded }}u;
|
| 53 |
+
const PARTIAL_COLS: u32 = {{ nPadded }}u;
|
| 54 |
+
const PARTIAL_SLICE_STRIDE: u32 = {{ batchCount }}u * PARTIAL_ROWS * PARTIAL_COLS;
|
| 55 |
+
const COUNT: u32 = {{ batchCount }}u * M * N;
|
| 56 |
+
const WORKGROUP_SIZE: u32 = {{ reduceWorkgroupSize }}u;
|
| 57 |
+
|
| 58 |
+
@compute @workgroup_size({{ reduceWorkgroupSize }}, 1, 1)
|
| 59 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 60 |
+
// 2D-folded flat index: gid.y carries the high bits past
|
| 61 |
+
// the per-axis dispatch fold width and reduces to the 1D form at y=0.
|
| 62 |
+
let idx = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WORKGROUP_SIZE;
|
| 63 |
+
if (idx >= COUNT) {
|
| 64 |
+
return;
|
| 65 |
+
}
|
| 66 |
+
let col = idx % N;
|
| 67 |
+
let row = (idx / N) % M;
|
| 68 |
+
let batch = idx / (M * N);
|
| 69 |
+
|
| 70 |
+
var src = (batch * PARTIAL_ROWS + row) * PARTIAL_COLS + col;
|
| 71 |
+
var acc = 0.0;
|
| 72 |
+
for (var z = 0u; z < SPLIT_K; z = z + 1u) {
|
| 73 |
+
acc = acc + partial[src];
|
| 74 |
+
src = src + PARTIAL_SLICE_STRIDE;
|
| 75 |
+
}
|
| 76 |
+
{% if hasBias %}
|
| 77 |
+
acc = acc + f32(bias[row]);
|
| 78 |
+
{% endif %}
|
| 79 |
+
{% if applyActivation %}
|
| 80 |
+
acc = fused_act(acc);
|
| 81 |
+
{% endif %}
|
| 82 |
+
y[idx] = {{ T }}(acc);
|
| 83 |
+
}
|
build/webgpu/conv1d-tiled-reg.wgsl.jinja
CHANGED
|
@@ -1,48 +1,43 @@
|
|
| 1 |
-
//
|
| 2 |
-
// W (M x K) @ im2col(X[b]) (K x N)
|
| 3 |
-
// kernelW, N
|
| 4 |
-
//
|
| 5 |
-
//
|
| 6 |
-
//
|
| 7 |
-
// workgroup computes a (WG_Y*TM) x (WG_X*TN) output tile
|
| 8 |
-
// slices,
|
| 9 |
-
// and
|
| 10 |
-
//
|
| 11 |
-
// activation epilogue
|
| 12 |
-
// store. f16 operands stay packed in workgroup memory and widen at the FMA
|
| 13 |
-
// site. Both tiles are indexed by their own output axis and group four K values
|
| 14 |
-
// per vector word, so the micro-tile accumulates through dot() and the implicit
|
| 15 |
-
// gather decodes one (ic, kw) tap per word, walking the remaining three. The
|
| 16 |
-
// micro-tile is unrolled into named registers: a dynamically indexed private
|
| 17 |
-
// array of vector words is large enough at the wide-M geometry that it may fail
|
| 18 |
-
// to scalarize and spill the accumulators.
|
| 19 |
-
{% if usesF16 %}
|
| 20 |
-
enable f16;
|
| 21 |
-
{% endif %}
|
| 22 |
{{ env.wgsl.resourceDeclarations }}
|
| 23 |
{% set hasActivation = hasActivation is defined and hasActivation %}
|
|
|
|
|
|
|
|
|
|
| 24 |
{% if hasActivation %}
|
| 25 |
// Apply the fused activation in the f32 accumulator before the single output
|
| 26 |
// cast, avoiding an intermediate convolution tensor.
|
| 27 |
-
|
| 28 |
-
{% if
|
| 29 |
return max(v, 0.0);
|
| 30 |
-
{% elif
|
| 31 |
-
return clamp(v, f32({{
|
| 32 |
-
{% elif
|
| 33 |
-
return select(v * f32({{
|
| 34 |
-
{% elif
|
| 35 |
return 1.0 / (1.0 + exp(-v));
|
| 36 |
-
{% elif
|
| 37 |
-
//
|
| 38 |
-
//
|
| 39 |
-
// already +/-1 to full f32 precision by |v| ~ 9.
|
| 40 |
return tanh(clamp(v, -10.0, 10.0));
|
| 41 |
-
{% elif
|
| 42 |
-
return clamp(f32({{
|
| 43 |
{% else %}
|
| 44 |
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 45 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
| 46 |
}
|
| 47 |
{% endif %}
|
| 48 |
|
|
|
|
| 1 |
+
// Register-tiled implicit GEMM for group-1 NCW convolution:
|
| 2 |
+
// Y[b] (M x N) = W (M x K) @ im2col(X[b]) (K x N), where M is outChannels, K
|
| 3 |
+
// is inChannels * kernelW, and N is outW. The X tile is gathered directly from
|
| 4 |
+
// NCW with the configured stride, dilation, and left padding; no column matrix
|
| 5 |
+
// is materialized.
|
| 6 |
+
//
|
| 7 |
+
// A WG_X x WG_Y workgroup computes a (WG_Y * TM) x (WG_X * TN) output tile
|
| 8 |
+
// from BK-deep slices, with one TM x TN register micro-tile per thread. Weight
|
| 9 |
+
// and input tiles group four K values per workgroup-memory word and accumulate
|
| 10 |
+
// through dot(). Bounds checks cover every partial tile. Accumulation, bias,
|
| 11 |
+
// and the optional activation epilogue use f32; f16 operands widen at the FMA.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
{{ env.wgsl.resourceDeclarations }}
|
| 13 |
{% set hasActivation = hasActivation is defined and hasActivation %}
|
| 14 |
+
{% set activation = activation | default("") %}
|
| 15 |
+
{% set actAlpha = actAlpha | default(0.0) %}
|
| 16 |
+
{% set actBeta = actBeta | default(0.0) %}
|
| 17 |
{% if hasActivation %}
|
| 18 |
// Apply the fused activation in the f32 accumulator before the single output
|
| 19 |
// cast, avoiding an intermediate convolution tensor.
|
| 20 |
+
{% macro fused_act_return(mode, alpha, beta) -%}
|
| 21 |
+
{% if mode == "Relu" %}
|
| 22 |
return max(v, 0.0);
|
| 23 |
+
{% elif mode == "Clip" %}
|
| 24 |
+
return clamp(v, f32({{ alpha }}), f32({{ beta }}));
|
| 25 |
+
{% elif mode == "LeakyRelu" %}
|
| 26 |
+
return select(v * f32({{ alpha }}), v, v >= 0.0);
|
| 27 |
+
{% elif mode == "Sigmoid" %}
|
| 28 |
return 1.0 / (1.0 + exp(-v));
|
| 29 |
+
{% elif mode == "Tanh" %}
|
| 30 |
+
// tanh is already +/-1 to f32 precision at the clamp bounds. Clamping before
|
| 31 |
+
// the builtin preserves that saturated result for larger accumulators.
|
|
|
|
| 32 |
return tanh(clamp(v, -10.0, 10.0));
|
| 33 |
+
{% elif mode == "HardSigmoid" %}
|
| 34 |
+
return clamp(f32({{ alpha }}) * v + f32({{ beta }}), 0.0, 1.0);
|
| 35 |
{% else %}
|
| 36 |
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 37 |
{% endif %}
|
| 38 |
+
{%- endmacro -%}
|
| 39 |
+
fn fused_act(v: f32) -> f32 {
|
| 40 |
+
{{ fused_act_return(activation, actAlpha, actBeta) -}}
|
| 41 |
}
|
| 42 |
{% endif %}
|
| 43 |
|
build/webgpu/conv2d-grouped-large-w4.wgsl.jinja
CHANGED
|
@@ -1,30 +1,32 @@
|
|
| 1 |
-
{% if usesF16 %}
|
| 2 |
-
enable f16;
|
| 3 |
-
{% endif %}
|
| 4 |
{{ env.wgsl.resourceDeclarations }}
|
| 5 |
{% set hasActivation = hasActivation is defined and hasActivation %}
|
|
|
|
|
|
|
|
|
|
| 6 |
{% if hasActivation %}
|
| 7 |
// Apply the fused activation in the f32 accumulator before the single output
|
| 8 |
// cast, avoiding an intermediate convolution tensor.
|
| 9 |
-
|
| 10 |
-
{% if
|
| 11 |
return max(v, 0.0);
|
| 12 |
-
{% elif
|
| 13 |
-
return clamp(v, f32({{
|
| 14 |
-
{% elif
|
| 15 |
-
return select(v * f32({{
|
| 16 |
-
{% elif
|
| 17 |
return 1.0 / (1.0 + exp(-v));
|
| 18 |
-
{% elif
|
| 19 |
-
//
|
| 20 |
-
//
|
| 21 |
-
// already +/-1 to full f32 precision by |v| ~ 9.
|
| 22 |
return tanh(clamp(v, -10.0, 10.0));
|
| 23 |
-
{% elif
|
| 24 |
-
return clamp(f32({{
|
| 25 |
{% else %}
|
| 26 |
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 27 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
| 28 |
}
|
| 29 |
{% endif %}
|
| 30 |
|
|
@@ -32,86 +34,125 @@ fn fused_act(v: f32) -> f32 {
|
|
| 32 |
// value for neighboring output columns and input window for neighboring output
|
| 33 |
// channels. One invocation computes four adjacent columns for OC_TILE channels,
|
| 34 |
// sharing each input load across those channels. Shape and window geometry are
|
| 35 |
-
// static, so
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
{%
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
{% endif %}
|
| 40 |
-
const
|
| 41 |
-
const
|
| 42 |
-
const
|
| 43 |
-
const OC_TILE: u32 = {{ source.ocTile }}u;
|
| 44 |
const OUT_C_TILES: u32 = OUT_C / OC_TILE;
|
| 45 |
-
const OUT_CPG: u32 = {{
|
| 46 |
-
const IN_C: u32 = {{
|
| 47 |
-
const IN_CPG: u32 = {{
|
| 48 |
-
const IN_H: i32 = {{
|
| 49 |
-
const IN_W: i32 = {{
|
| 50 |
-
const IN_H_U: u32 = {{
|
| 51 |
-
const IN_W_U: u32 = {{
|
| 52 |
const IN_PLANE: u32 = IN_H_U * IN_W_U;
|
| 53 |
-
{% if not
|
| 54 |
{% endif %}
|
| 55 |
-
const KAREA: u32 = {{
|
| 56 |
-
const STRIDE_H: u32 = {{
|
| 57 |
-
const STRIDE_W: i32 = {{
|
| 58 |
-
const PAD_TOP: i32 = {{
|
| 59 |
-
const PAD_LEFT: i32 = {{
|
| 60 |
-
const WG: u32 = {{
|
| 61 |
|
| 62 |
-
@compute @workgroup_size({{
|
| 63 |
-
fn main(@builtin(global_invocation_id) gid: vec3<u32>
|
| 64 |
-
let q = gid.x + gid.y *
|
| 65 |
if (q >= COUNT_TILES) { return; }
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
let ow4 = q % OUT_W4;
|
| 68 |
var t = q / OUT_W4;
|
|
|
|
| 69 |
let oh = t % OUT_H;
|
| 70 |
t /= OUT_H;
|
| 71 |
let ocBase = (t % OUT_C_TILES) * OC_TILE;
|
| 72 |
let batch = t / OUT_C_TILES;
|
| 73 |
let group = ocBase / OUT_CPG;
|
| 74 |
let ihBase = i32(oh * STRIDE_H) - PAD_TOP;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
let iwBase = i32(ow4 * 4u) * STRIDE_W - PAD_LEFT;
|
|
|
|
| 76 |
var xChannelBase = (batch * IN_C + group * IN_CPG) * IN_PLANE;
|
| 77 |
-
{% for oct in range(
|
| 78 |
var wBase{{ oct }} = (ocBase + {{ oct }}u) * IN_CPG * KAREA;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
var acc{{ oct }} = vec4<f32>(0.0);
|
|
|
|
| 80 |
{% endfor %}
|
| 81 |
|
| 82 |
for (var ic = 0u; ic < IN_CPG; ic++) {
|
| 83 |
-
{% for kh in range(
|
| 84 |
{
|
| 85 |
-
let ih = ihBase + {{ kh *
|
| 86 |
if (ih >= 0 && ih < IN_H) {
|
| 87 |
let xRow = xChannelBase + u32(ih) * IN_W_U;
|
| 88 |
-
{% if
|
| 89 |
// The four output columns and the kernel taps overlap heavily: tap kw of
|
| 90 |
// lane l reads input column kw*dilationW + l*strideW, so kernelW*4 reads
|
| 91 |
// land on only SPAN distinct columns. Load the row window once and let
|
| 92 |
-
// every tap take its value from a
|
| 93 |
-
// zero
|
| 94 |
-
// per-tap bounds test did.
|
| 95 |
//
|
| 96 |
-
// SPAN grows as (kernelW-1)*dilationW
|
| 97 |
-
//
|
| 98 |
-
|
| 99 |
-
{% for s in range(source.span) %}
|
| 100 |
{% set used = namespace(value=false) %}
|
| 101 |
-
{% for kw in range(
|
| 102 |
-
{% for lane in range(
|
| 103 |
-
{% if s == kw *
|
| 104 |
{% endfor %}
|
| 105 |
{% endfor %}
|
| 106 |
{% if used.value %}
|
| 107 |
var xw{{ s }} = 0.0;
|
| 108 |
{% endif %}
|
| 109 |
{% endfor %}
|
| 110 |
-
{% for s in range(
|
| 111 |
{% set used = namespace(value=false) %}
|
| 112 |
-
{% for kw in range(
|
| 113 |
-
{% for lane in range(
|
| 114 |
-
{% if s == kw *
|
| 115 |
{% endfor %}
|
| 116 |
{% endfor %}
|
| 117 |
{% if used.value %}
|
|
@@ -121,31 +162,31 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
|
|
| 121 |
}
|
| 122 |
{% endif %}
|
| 123 |
{% endfor %}
|
| 124 |
-
{% for kw in range(
|
| 125 |
{
|
| 126 |
-
{% for oct in range(
|
| 127 |
-
let weight{{ oct }} = f32(w[wBase{{ oct }} + {{ kh *
|
| 128 |
{% endfor %}
|
| 129 |
-
{% for lane in range(
|
| 130 |
-
{% for oct in range(
|
| 131 |
-
|
| 132 |
{% endfor %}
|
| 133 |
{% endfor %}
|
| 134 |
}
|
| 135 |
{% endfor %}
|
| 136 |
{% else %}
|
| 137 |
-
{% for kw in range(
|
| 138 |
{
|
| 139 |
-
{% for oct in range(
|
| 140 |
-
let weight{{ oct }} = f32(w[wBase{{ oct }} + {{ kh *
|
| 141 |
{% endfor %}
|
| 142 |
-
let iwK = iwBase + {{ kw *
|
| 143 |
-
{% for lane in range(
|
| 144 |
-
let iwLane{{ lane }} = iwK + {{ lane *
|
| 145 |
if (iwLane{{ lane }} >= 0 && iwLane{{ lane }} < IN_W) {
|
| 146 |
let xValue = f32(x[xRow + u32(iwLane{{ lane }})]);
|
| 147 |
-
{% for oct in range(
|
| 148 |
-
|
| 149 |
{% endfor %}
|
| 150 |
}
|
| 151 |
{% endfor %}
|
|
@@ -156,13 +197,23 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
|
|
| 156 |
}
|
| 157 |
{% endfor %}
|
| 158 |
xChannelBase += IN_PLANE;
|
| 159 |
-
{% for oct in range(
|
| 160 |
wBase{{ oct }} += KAREA;
|
| 161 |
{% endfor %}
|
| 162 |
}
|
| 163 |
-
{% if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
let outBase = ((batch * OUT_C + ocBase) * OUT_H + oh) * OUT_W + ow4 * 4u;
|
| 165 |
-
{% for oct in range(
|
| 166 |
let outChannelBase{{ oct }} = outBase + {{ oct }}u * OUT_H * OUT_W;
|
| 167 |
{% for lane in range(4) %}
|
| 168 |
if (ow4 * 4u + {{ lane }}u < OUT_W) {
|
|
@@ -172,7 +223,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
|
|
| 172 |
{% endfor %}
|
| 173 |
{% else %}
|
| 174 |
let outBase = ((batch * OUT_C + ocBase) * OUT_H + oh) * OUT_W4 + ow4;
|
| 175 |
-
{% for oct in range(
|
| 176 |
y[outBase + {{ oct }}u * OUT_PLANE4] = {{ vectorScalar }}({% if hasActivation %}vec4<f32>(fused_act(acc{{ oct }}.x), fused_act(acc{{ oct }}.y), fused_act(acc{{ oct }}.z), fused_act(acc{{ oct }}.w)){% else %}acc{{ oct }}{% endif %});
|
| 177 |
{% endfor %}
|
| 178 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
{{ env.wgsl.resourceDeclarations }}
|
| 2 |
{% set hasActivation = hasActivation is defined and hasActivation %}
|
| 3 |
+
{% set activation = activation | default("") %}
|
| 4 |
+
{% set actAlpha = actAlpha | default(0.0) %}
|
| 5 |
+
{% set actBeta = actBeta | default(0.0) %}
|
| 6 |
{% if hasActivation %}
|
| 7 |
// Apply the fused activation in the f32 accumulator before the single output
|
| 8 |
// cast, avoiding an intermediate convolution tensor.
|
| 9 |
+
{% macro fused_act_return(mode, alpha, beta) -%}
|
| 10 |
+
{% if mode == "Relu" %}
|
| 11 |
return max(v, 0.0);
|
| 12 |
+
{% elif mode == "Clip" %}
|
| 13 |
+
return clamp(v, f32({{ alpha }}), f32({{ beta }}));
|
| 14 |
+
{% elif mode == "LeakyRelu" %}
|
| 15 |
+
return select(v * f32({{ alpha }}), v, v >= 0.0);
|
| 16 |
+
{% elif mode == "Sigmoid" %}
|
| 17 |
return 1.0 / (1.0 + exp(-v));
|
| 18 |
+
{% elif mode == "Tanh" %}
|
| 19 |
+
// tanh is already +/-1 to f32 precision at the clamp bounds. Clamping before
|
| 20 |
+
// the builtin preserves that saturated result for larger accumulators.
|
|
|
|
| 21 |
return tanh(clamp(v, -10.0, 10.0));
|
| 22 |
+
{% elif mode == "HardSigmoid" %}
|
| 23 |
+
return clamp(f32({{ alpha }}) * v + f32({{ beta }}), 0.0, 1.0);
|
| 24 |
{% else %}
|
| 25 |
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 26 |
{% endif %}
|
| 27 |
+
{%- endmacro -%}
|
| 28 |
+
fn fused_act(v: f32) -> f32 {
|
| 29 |
+
{{ fused_act_return(activation, actAlpha, actBeta) -}}
|
| 30 |
}
|
| 31 |
{% endif %}
|
| 32 |
|
|
|
|
| 34 |
// value for neighboring output columns and input window for neighboring output
|
| 35 |
// channels. One invocation computes four adjacent columns for OC_TILE channels,
|
| 36 |
// sharing each input load across those channels. Shape and window geometry are
|
| 37 |
+
// static, so indexing divisors and kernel offsets are shader constants.
|
| 38 |
+
{% set dilatedLanes = dilatedLanes | default(false) %}
|
| 39 |
+
{% set lanes = lanes | default(4) %}
|
| 40 |
+
{% set quads = quads | default(1) %}
|
| 41 |
+
{% set laneStep = dilationWSpec if dilatedLanes else strideWSpec %}
|
| 42 |
+
{% set comps = ["x", "y", "z", "w"] %}
|
| 43 |
+
{% macro acc_ref(oct, lane) %}{% if dilatedLanes %}acc{{ oct }}_{{ lane }}{% else %}acc{{ oct }}.{{ comps[lane] }}{% endif %}{% endmacro %}
|
| 44 |
+
{% set usedCount = namespace(value=0) %}
|
| 45 |
+
{% for s in range(span) %}
|
| 46 |
+
{% set used = namespace(value=false) %}
|
| 47 |
+
{% for kw in range(kernelWSpec) %}
|
| 48 |
+
{% for lane in range(lanes) %}
|
| 49 |
+
{% if s == kw * dilationWSpec + lane * laneStep %}{% set used.value = true %}{% endif %}
|
| 50 |
+
{% endfor %}
|
| 51 |
+
{% endfor %}
|
| 52 |
+
{% if used.value %}{% set usedCount.value = usedCount.value + 1 %}{% endif %}
|
| 53 |
+
{% endfor %}
|
| 54 |
+
{% set registerForm = (usedCount.value <= spanCap) if dilatedLanes else (span <= spanCap) %}
|
| 55 |
+
const COUNT_TILES: u32 = {{ countTiles }}u;
|
| 56 |
+
{% if tailOutput %}const OUT_W: u32 = max(1u, {{ outW }}u);
|
| 57 |
+
{% endif %}
|
| 58 |
+
{% if dilatedLanes %}
|
| 59 |
+
const OUT_W: u32 = max(1u, {{ outW }}u);
|
| 60 |
+
const LANES: u32 = {{ lanes }}u;
|
| 61 |
+
const RESIDUES: u32 = {{ dilationWSpec }}u;
|
| 62 |
+
const TILES_PER_ROW: u32 = RESIDUES * {{ quads }}u;
|
| 63 |
+
{% else %}
|
| 64 |
+
const OUT_W4: u32 = max(1u, {{ outW4 }}u);
|
| 65 |
{% endif %}
|
| 66 |
+
const OUT_H: u32 = max(1u, {{ outH }}u);
|
| 67 |
+
const OUT_C: u32 = max(1u, {{ outC }}u);
|
| 68 |
+
const OC_TILE: u32 = {{ ocTile }}u;
|
|
|
|
| 69 |
const OUT_C_TILES: u32 = OUT_C / OC_TILE;
|
| 70 |
+
const OUT_CPG: u32 = {{ outCPerGroup }}u;
|
| 71 |
+
const IN_C: u32 = {{ inC }}u;
|
| 72 |
+
const IN_CPG: u32 = {{ inCPerGroup }}u;
|
| 73 |
+
const IN_H: i32 = {{ inH }};
|
| 74 |
+
const IN_W: i32 = {{ inW }};
|
| 75 |
+
const IN_H_U: u32 = {{ inH }}u;
|
| 76 |
+
const IN_W_U: u32 = {{ inW }}u;
|
| 77 |
const IN_PLANE: u32 = IN_H_U * IN_W_U;
|
| 78 |
+
{% if not tailOutput and not dilatedLanes %}const OUT_PLANE4: u32 = OUT_H * OUT_W4;
|
| 79 |
{% endif %}
|
| 80 |
+
const KAREA: u32 = {{ kernelHSpec * kernelWSpec }}u;
|
| 81 |
+
const STRIDE_H: u32 = {{ strideHSpec }}u;
|
| 82 |
+
const STRIDE_W: i32 = {{ strideWSpec }};
|
| 83 |
+
const PAD_TOP: i32 = {{ padTopSpec }};
|
| 84 |
+
const PAD_LEFT: i32 = {{ padLeftSpec }};
|
| 85 |
+
const WG: u32 = {{ workgroupSizeSpec }}u;
|
| 86 |
|
| 87 |
+
@compute @workgroup_size({{ workgroupSizeSpec }})
|
| 88 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 89 |
+
let q = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
|
| 90 |
if (q >= COUNT_TILES) { return; }
|
| 91 |
|
| 92 |
+
{% if dilatedLanes %}
|
| 93 |
+
let tile = q % TILES_PER_ROW;
|
| 94 |
+
var t = q / TILES_PER_ROW;
|
| 95 |
+
{% else %}
|
| 96 |
let ow4 = q % OUT_W4;
|
| 97 |
var t = q / OUT_W4;
|
| 98 |
+
{% endif %}
|
| 99 |
let oh = t % OUT_H;
|
| 100 |
t /= OUT_H;
|
| 101 |
let ocBase = (t % OUT_C_TILES) * OC_TILE;
|
| 102 |
let batch = t / OUT_C_TILES;
|
| 103 |
let group = ocBase / OUT_CPG;
|
| 104 |
let ihBase = i32(oh * STRIDE_H) - PAD_TOP;
|
| 105 |
+
{% if dilatedLanes %}
|
| 106 |
+
// Lane l owns output column ow0 + l * dilationW: the residue class first,
|
| 107 |
+
// then the LANES-wide run within it. Runs past the row store nothing.
|
| 108 |
+
let ow0 = tile % RESIDUES + (tile / RESIDUES) * (LANES * RESIDUES);
|
| 109 |
+
let iwBase = i32(ow0) * STRIDE_W - PAD_LEFT;
|
| 110 |
+
{% else %}
|
| 111 |
let iwBase = i32(ow4 * 4u) * STRIDE_W - PAD_LEFT;
|
| 112 |
+
{% endif %}
|
| 113 |
var xChannelBase = (batch * IN_C + group * IN_CPG) * IN_PLANE;
|
| 114 |
+
{% for oct in range(ocTile) %}
|
| 115 |
var wBase{{ oct }} = (ocBase + {{ oct }}u) * IN_CPG * KAREA;
|
| 116 |
+
{% if dilatedLanes %}
|
| 117 |
+
{% for lane in range(lanes) %}
|
| 118 |
+
var acc{{ oct }}_{{ lane }} = 0.0;
|
| 119 |
+
{% endfor %}
|
| 120 |
+
{% else %}
|
| 121 |
var acc{{ oct }} = vec4<f32>(0.0);
|
| 122 |
+
{% endif %}
|
| 123 |
{% endfor %}
|
| 124 |
|
| 125 |
for (var ic = 0u; ic < IN_CPG; ic++) {
|
| 126 |
+
{% for kh in range(kernelHSpec) %}
|
| 127 |
{
|
| 128 |
+
let ih = ihBase + {{ kh * dilationHSpec }};
|
| 129 |
if (ih >= 0 && ih < IN_H) {
|
| 130 |
let xRow = xChannelBase + u32(ih) * IN_W_U;
|
| 131 |
+
{% if registerForm %}
|
| 132 |
// The four output columns and the kernel taps overlap heavily: tap kw of
|
| 133 |
// lane l reads input column kw*dilationW + l*strideW, so kernelW*4 reads
|
| 134 |
// land on only SPAN distinct columns. Load the row window once and let
|
| 135 |
+
// every tap take its value from a statically named local. Out-of-range
|
| 136 |
+
// columns contain zero and contribute nothing to the accumulation.
|
|
|
|
| 137 |
//
|
| 138 |
+
// SPAN grows as (kernelW-1)*dilationW. `spanCap` bounds the number of
|
| 139 |
+
// staged row-window values; wider spans use the fixed-size per-tap form below.
|
| 140 |
+
{% for s in range(span) %}
|
|
|
|
| 141 |
{% set used = namespace(value=false) %}
|
| 142 |
+
{% for kw in range(kernelWSpec) %}
|
| 143 |
+
{% for lane in range(lanes) %}
|
| 144 |
+
{% if s == kw * dilationWSpec + lane * laneStep %}{% set used.value = true %}{% endif %}
|
| 145 |
{% endfor %}
|
| 146 |
{% endfor %}
|
| 147 |
{% if used.value %}
|
| 148 |
var xw{{ s }} = 0.0;
|
| 149 |
{% endif %}
|
| 150 |
{% endfor %}
|
| 151 |
+
{% for s in range(span) %}
|
| 152 |
{% set used = namespace(value=false) %}
|
| 153 |
+
{% for kw in range(kernelWSpec) %}
|
| 154 |
+
{% for lane in range(lanes) %}
|
| 155 |
+
{% if s == kw * dilationWSpec + lane * laneStep %}{% set used.value = true %}{% endif %}
|
| 156 |
{% endfor %}
|
| 157 |
{% endfor %}
|
| 158 |
{% if used.value %}
|
|
|
|
| 162 |
}
|
| 163 |
{% endif %}
|
| 164 |
{% endfor %}
|
| 165 |
+
{% for kw in range(kernelWSpec) %}
|
| 166 |
{
|
| 167 |
+
{% for oct in range(ocTile) %}
|
| 168 |
+
let weight{{ oct }} = f32(w[wBase{{ oct }} + {{ kh * kernelWSpec + kw }}u]);
|
| 169 |
{% endfor %}
|
| 170 |
+
{% for lane in range(lanes) %}
|
| 171 |
+
{% for oct in range(ocTile) %}
|
| 172 |
+
{{ acc_ref(oct, lane) }} += xw{{ kw * dilationWSpec + lane * laneStep }} * weight{{ oct }};
|
| 173 |
{% endfor %}
|
| 174 |
{% endfor %}
|
| 175 |
}
|
| 176 |
{% endfor %}
|
| 177 |
{% else %}
|
| 178 |
+
{% for kw in range(kernelWSpec) %}
|
| 179 |
{
|
| 180 |
+
{% for oct in range(ocTile) %}
|
| 181 |
+
let weight{{ oct }} = f32(w[wBase{{ oct }} + {{ kh * kernelWSpec + kw }}u]);
|
| 182 |
{% endfor %}
|
| 183 |
+
let iwK = iwBase + {{ kw * dilationWSpec }};
|
| 184 |
+
{% for lane in range(lanes) %}
|
| 185 |
+
let iwLane{{ lane }} = iwK + {{ lane * laneStep }};
|
| 186 |
if (iwLane{{ lane }} >= 0 && iwLane{{ lane }} < IN_W) {
|
| 187 |
let xValue = f32(x[xRow + u32(iwLane{{ lane }})]);
|
| 188 |
+
{% for oct in range(ocTile) %}
|
| 189 |
+
{{ acc_ref(oct, lane) }} += xValue * weight{{ oct }};
|
| 190 |
{% endfor %}
|
| 191 |
}
|
| 192 |
{% endfor %}
|
|
|
|
| 197 |
}
|
| 198 |
{% endfor %}
|
| 199 |
xChannelBase += IN_PLANE;
|
| 200 |
+
{% for oct in range(ocTile) %}
|
| 201 |
wBase{{ oct }} += KAREA;
|
| 202 |
{% endfor %}
|
| 203 |
}
|
| 204 |
+
{% if dilatedLanes %}
|
| 205 |
+
let outBase = ((batch * OUT_C + ocBase) * OUT_H + oh) * OUT_W;
|
| 206 |
+
{% for oct in range(ocTile) %}
|
| 207 |
+
let outChannelBase{{ oct }} = outBase + {{ oct }}u * OUT_H * OUT_W;
|
| 208 |
+
{% for lane in range(lanes) %}
|
| 209 |
+
if (ow0 + {{ lane * dilationWSpec }}u < OUT_W) {
|
| 210 |
+
y[outChannelBase{{ oct }} + ow0 + {{ lane * dilationWSpec }}u] = {{ scalar }}({% if hasActivation %}fused_act(acc{{ oct }}_{{ lane }}){% else %}acc{{ oct }}_{{ lane }}{% endif %});
|
| 211 |
+
}
|
| 212 |
+
{% endfor %}
|
| 213 |
+
{% endfor %}
|
| 214 |
+
{% elif tailOutput %}
|
| 215 |
let outBase = ((batch * OUT_C + ocBase) * OUT_H + oh) * OUT_W + ow4 * 4u;
|
| 216 |
+
{% for oct in range(ocTile) %}
|
| 217 |
let outChannelBase{{ oct }} = outBase + {{ oct }}u * OUT_H * OUT_W;
|
| 218 |
{% for lane in range(4) %}
|
| 219 |
if (ow4 * 4u + {{ lane }}u < OUT_W) {
|
|
|
|
| 223 |
{% endfor %}
|
| 224 |
{% else %}
|
| 225 |
let outBase = ((batch * OUT_C + ocBase) * OUT_H + oh) * OUT_W4 + ow4;
|
| 226 |
+
{% for oct in range(ocTile) %}
|
| 227 |
y[outBase + {{ oct }}u * OUT_PLANE4] = {{ vectorScalar }}({% if hasActivation %}vec4<f32>(fused_act(acc{{ oct }}.x), fused_act(acc{{ oct }}.y), fused_act(acc{{ oct }}.z), fused_act(acc{{ oct }}.w)){% else %}acc{{ oct }}{% endif %});
|
| 228 |
{% endfor %}
|
| 229 |
{% endif %}
|
build/webgpu/manifest.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/webgpu/metadata.json
CHANGED
|
@@ -1,25 +1,91 @@
|
|
| 1 |
{
|
| 2 |
"name": "com.microsoft.FusedConv",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
-
"bench.json": "
|
| 11 |
-
"conv-1x1-gemm-tiled-reg.wgsl.jinja": "
|
| 12 |
-
"conv-1x1-gemm-tiled.wgsl.jinja": "
|
| 13 |
-
"conv-1x1-subgroup-matrix.wgsl.jinja": "
|
| 14 |
-
"conv-direct-nd.wgsl.jinja": "
|
| 15 |
-
"conv-direct-unrolled.wgsl.jinja": "
|
| 16 |
-
"conv-im2col-nchw.wgsl.jinja": "
|
| 17 |
-
"
|
| 18 |
-
"
|
| 19 |
-
"
|
| 20 |
-
"
|
|
|
|
| 21 |
}
|
| 22 |
},
|
| 23 |
-
"provenance": { "kernel": { "sha": "
|
| 24 |
-
"webgpu": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "com.microsoft.FusedConv",
|
| 3 |
+
"id": "_com_microsoft_fusedconv_webgpu_e4dcf04",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
+
"bench.json": "rDPb1+xln8/qfupw2ajiwaBUEKJvlo+NuJEs81cAgTk=",
|
| 11 |
+
"conv-1x1-gemm-tiled-reg.wgsl.jinja": "9yFpwXS1HzmWmm4W72n+aOIryLqc9+e88awZ1ogTe+Q=",
|
| 12 |
+
"conv-1x1-gemm-tiled.wgsl.jinja": "uqG5mRVvcHiUo0LDCUcVgqElVTvRNknx19Gm4oIALiU=",
|
| 13 |
+
"conv-1x1-subgroup-matrix.wgsl.jinja": "iDJs4RJk4iJarOZCjY7/5MFrHgR7CcqI6x5uwv2dYaA=",
|
| 14 |
+
"conv-direct-nd.wgsl.jinja": "5UA6BigLbtPRMre3LRfNShldvupB5yTGn8EzX2tBzP8=",
|
| 15 |
+
"conv-direct-unrolled.wgsl.jinja": "vrGUPwYkbYE1slII7nuK5wbERRcG5u0X1cW1kMc2sQs=",
|
| 16 |
+
"conv-im2col-nchw.wgsl.jinja": "IRpmtpeVQsLE6ud+Sf1230XaZVS1dxDtTZuSDwU3P8s=",
|
| 17 |
+
"conv-splitk-reduce.wgsl.jinja": "LbVU5DRL0Q51fvLjThmxJQZH8aPbZkvNzij4nY/cHmo=",
|
| 18 |
+
"conv1d-tiled-reg.wgsl.jinja": "ERqOPl+1RbnJ+CFqZvLSYw45qYMKgB7Je0teew5gyOM=",
|
| 19 |
+
"conv2d-grouped-large-w4.wgsl.jinja": "qBG2cfOsQ4FMwtNT8wBOqLJFDOs6w0wJCmfcZuxbho8=",
|
| 20 |
+
"manifest.json": "EpZ3lQZ0Ar7ZY4xDkUlcQ3m6lyw6x6+cp9KoBC47TSE=",
|
| 21 |
+
"test.json": "g7doPXd+oCU97EVAbYReA8qInOswdv5wrLcMYCX7CcM="
|
| 22 |
}
|
| 23 |
},
|
| 24 |
+
"provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
|
| 25 |
+
"webgpu": {
|
| 26 |
+
"manifestSpec": "2.0",
|
| 27 |
+
"variants": {
|
| 28 |
+
"implicit_im2col_tiled_reg_splitk": ["conv-1x1-gemm-tiled-reg.wgsl.jinja", "conv-splitk-reduce.wgsl.jinja"],
|
| 29 |
+
"implicit_im2col_tiled_bias_reg_splitk": ["conv-1x1-gemm-tiled-reg.wgsl.jinja", "conv-splitk-reduce.wgsl.jinja"],
|
| 30 |
+
"implicit_im2col_tiled_reg_splitk_preferred": ["conv-1x1-gemm-tiled-reg.wgsl.jinja", "conv-splitk-reduce.wgsl.jinja"],
|
| 31 |
+
"implicit_im2col_tiled_bias_reg_splitk_preferred": ["conv-1x1-gemm-tiled-reg.wgsl.jinja", "conv-splitk-reduce.wgsl.jinja"],
|
| 32 |
+
"implicit_im2col_tiled_reg": ["conv-1x1-gemm-tiled-reg.wgsl.jinja"],
|
| 33 |
+
"implicit_im2col_tiled_bias_reg": ["conv-1x1-gemm-tiled-reg.wgsl.jinja"],
|
| 34 |
+
"gemm_1x1_subgroup_matrix": ["conv-1x1-subgroup-matrix.wgsl.jinja"],
|
| 35 |
+
"gemm_1x1_subgroup_matrix_z": ["conv-1x1-subgroup-matrix.wgsl.jinja"],
|
| 36 |
+
"gemm_1x1_subgroup_matrix_bias": ["conv-1x1-subgroup-matrix.wgsl.jinja"],
|
| 37 |
+
"gemm_1x1_subgroup_matrix_bias_z": ["conv-1x1-subgroup-matrix.wgsl.jinja"],
|
| 38 |
+
"im2col_gemm_subgroup_matrix": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 39 |
+
"im2col_direct_inputs_subgroup_matrix": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 40 |
+
"im2col_gemm_subgroup_matrix_z": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 41 |
+
"im2col_direct_inputs_subgroup_matrix_z": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 42 |
+
"im2col_gemm_subgroup_matrix_bias": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 43 |
+
"im2col_direct_inputs_subgroup_matrix_bias": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 44 |
+
"im2col_gemm_subgroup_matrix_bias_z": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 45 |
+
"im2col_direct_inputs_subgroup_matrix_bias_z": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 46 |
+
"im2col_half_direct_subgroup_matrix": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 47 |
+
"im2col_half_direct_subgroup_matrix_z": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 48 |
+
"im2col_half_direct_subgroup_matrix_bias": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 49 |
+
"im2col_half_direct_subgroup_matrix_bias_z": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 50 |
+
"im2col_gemm_subgroup_matrix_padded": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 51 |
+
"im2col_gemm_subgroup_matrix_padded_z": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 52 |
+
"im2col_gemm_subgroup_matrix_padded_bias": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 53 |
+
"im2col_gemm_subgroup_matrix_padded_bias_z": ["conv-1x1-subgroup-matrix.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 54 |
+
"im2col_gemm_tiled": ["conv-1x1-gemm-tiled.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 55 |
+
"im2col_gemm_tiled_z": ["conv-1x1-gemm-tiled.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 56 |
+
"im2col_gemm_tiled_bias": ["conv-1x1-gemm-tiled.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 57 |
+
"im2col_gemm_tiled_bias_z": ["conv-1x1-gemm-tiled.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 58 |
+
"im2col_gemm_tiled_reg": ["conv-1x1-gemm-tiled-reg.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 59 |
+
"im2col_gemm_tiled_bias_reg": ["conv-1x1-gemm-tiled-reg.wgsl.jinja", "conv-im2col-nchw.wgsl.jinja"],
|
| 60 |
+
"gemm_1x1_tiled": ["conv-1x1-gemm-tiled.wgsl.jinja"],
|
| 61 |
+
"gemm_1x1_tiled_z": ["conv-1x1-gemm-tiled.wgsl.jinja"],
|
| 62 |
+
"gemm_1x1_tiled_bias": ["conv-1x1-gemm-tiled.wgsl.jinja"],
|
| 63 |
+
"gemm_1x1_tiled_bias_z": ["conv-1x1-gemm-tiled.wgsl.jinja"],
|
| 64 |
+
"gemm_1x1_tiled_reg": ["conv-1x1-gemm-tiled-reg.wgsl.jinja"],
|
| 65 |
+
"gemm_1x1_tiled_bias_reg": ["conv-1x1-gemm-tiled-reg.wgsl.jinja"],
|
| 66 |
+
"conv1d_direct": ["conv-direct-unrolled.wgsl.jinja"],
|
| 67 |
+
"direct_unrolled": ["conv-direct-unrolled.wgsl.jinja"],
|
| 68 |
+
"direct_unrolled_z": ["conv-direct-unrolled.wgsl.jinja"],
|
| 69 |
+
"conv1d_direct_bias": ["conv-direct-unrolled.wgsl.jinja"],
|
| 70 |
+
"direct_unrolled_bias": ["conv-direct-unrolled.wgsl.jinja"],
|
| 71 |
+
"direct_unrolled_bias_z": ["conv-direct-unrolled.wgsl.jinja"],
|
| 72 |
+
"conv1d_tiled_reg": ["conv1d-tiled-reg.wgsl.jinja"],
|
| 73 |
+
"conv1d_tiled_bias_reg": ["conv1d-tiled-reg.wgsl.jinja"],
|
| 74 |
+
"ncdhw3d": ["conv-direct-nd.wgsl.jinja"],
|
| 75 |
+
"ncdhw3d_z": ["conv-direct-nd.wgsl.jinja"],
|
| 76 |
+
"ncdhw3d_bias": ["conv-direct-nd.wgsl.jinja"],
|
| 77 |
+
"ncdhw3d_bias_z": ["conv-direct-nd.wgsl.jinja"],
|
| 78 |
+
"grouped_large_kernel_w4": ["conv2d-grouped-large-w4.wgsl.jinja"],
|
| 79 |
+
"grouped_large_kernel_w4_tail": ["conv2d-grouped-large-w4.wgsl.jinja"],
|
| 80 |
+
"grouped_large_kernel_w4_dilated_lanes": ["conv2d-grouped-large-w4.wgsl.jinja"],
|
| 81 |
+
"implicit_im2col_subgroup_matrix": ["conv-1x1-subgroup-matrix.wgsl.jinja"],
|
| 82 |
+
"implicit_im2col_subgroup_matrix_z": ["conv-1x1-subgroup-matrix.wgsl.jinja"],
|
| 83 |
+
"implicit_im2col_subgroup_matrix_bias": ["conv-1x1-subgroup-matrix.wgsl.jinja"],
|
| 84 |
+
"implicit_im2col_subgroup_matrix_bias_z": ["conv-1x1-subgroup-matrix.wgsl.jinja"],
|
| 85 |
+
"implicit_im2col_subgroup_matrix_f16": ["conv-1x1-subgroup-matrix.wgsl.jinja"],
|
| 86 |
+
"implicit_im2col_subgroup_matrix_z_f16": ["conv-1x1-subgroup-matrix.wgsl.jinja"],
|
| 87 |
+
"implicit_im2col_subgroup_matrix_bias_f16": ["conv-1x1-subgroup-matrix.wgsl.jinja"],
|
| 88 |
+
"implicit_im2col_subgroup_matrix_bias_z_f16": ["conv-1x1-subgroup-matrix.wgsl.jinja"]
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
}
|
build/webgpu/test.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|