sync 2e7068faf55e
Browse files- README.md +88 -0
- build/webgpu/bench.json +922 -0
- build/webgpu/conv-1x1-gemm-tiled-reg.wgsl.jinja +419 -0
- build/webgpu/conv-1x1-gemm-tiled.wgsl.jinja +138 -0
- build/webgpu/conv-1x1-subgroup-matrix.wgsl.jinja +353 -0
- build/webgpu/conv-direct-nd.wgsl.jinja +86 -0
- build/webgpu/conv-direct-unrolled.wgsl.jinja +95 -0
- build/webgpu/conv-im2col-nchw.wgsl.jinja +73 -0
- build/webgpu/conv1d-tiled-reg.wgsl.jinja +166 -0
- build/webgpu/conv2d-grouped-large-w4.wgsl.jinja +176 -0
- build/webgpu/manifest.json +0 -0
- build/webgpu/metadata.json +25 -0
- build/webgpu/test.json +0 -0
README.md
CHANGED
|
@@ -1,3 +1,91 @@
|
|
| 1 |
---
|
|
|
|
| 2 |
license: apache-2.0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
library_name: kernels
|
| 3 |
license: apache-2.0
|
| 4 |
+
tags:
|
| 5 |
+
- kernel
|
| 6 |
+
- webgpu
|
| 7 |
+
- wgsl
|
| 8 |
---
|
| 9 |
+
# com.microsoft.FusedConv
|
| 10 |
+
|
| 11 |
+
`com.microsoft` · ONNX Runtime contrib operator · contrib since_version 1
|
| 12 |
+
|
| 13 |
+
## Description
|
| 14 |
+
|
| 15 |
+
Applies an N-dimensional convolution with optional bias `B` and residual `Z`, followed by an optional fused activation. Omitting `activation` leaves the convolution result unchanged. Supported activations are `Relu`, `LeakyRelu`, `Sigmoid`, `Tanh`, `HardSigmoid`, `HardSwish`, and `Clip`; other schema-permitted activation strings are not implemented. The implementation supports one to three spatial dimensions and float16 or float32; higher spatial ranks and float64 are not implemented.
|
| 16 |
+
|
| 17 |
+
See the [ONNX Runtime `FusedConv` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.FusedConv) for the reference semantics.
|
| 18 |
+
|
| 19 |
+
## Inputs
|
| 20 |
+
|
| 21 |
+
| Name | Bind key | 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 |
+
| `B` | `bias` | `T` | `1` | — | Optional 1-D bias tensor of length `out_channels`, broadcast-added to each output channel. | optional |
|
| 26 |
+
| `Z` | `zResidual` | `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 | Bind key | Logical dtype | Rank | Shape | Description | Presence |
|
| 31 |
+
| --- | --- | --- | --- | --- | --- | --- |
|
| 32 |
+
| `Y` | `y` | `T` | same as `X` | derived; see description | Output feature map tensor after convolution, optional bias/residual addition, and the fused activation. | required |
|
| 33 |
+
|
| 34 |
+
## Attributes
|
| 35 |
+
|
| 36 |
+
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. |
|
| 48 |
+
|
| 49 |
+
## Type constraints
|
| 50 |
+
|
| 51 |
+
| Variable | Allowed dtypes |
|
| 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
|
| 65 |
+
- [`conv-1x1-gemm-tiled-reg.wgsl.jinja`](build/webgpu/conv-1x1-gemm-tiled-reg.wgsl.jinja)
|
| 66 |
+
- [`conv-1x1-gemm-tiled.wgsl.jinja`](build/webgpu/conv-1x1-gemm-tiled.wgsl.jinja)
|
| 67 |
+
- [`conv-1x1-subgroup-matrix.wgsl.jinja`](build/webgpu/conv-1x1-subgroup-matrix.wgsl.jinja)
|
| 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 |
+
The loader derives every required output's shape and logical dtype from the manifest contract and this call.
|
| 77 |
+
It then allocates the result tensors automatically.
|
| 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 |
+
|
| 83 |
+
```js
|
| 84 |
+
import { getKernel } from "@huggingface/kernels";
|
| 85 |
+
|
| 86 |
+
const kernel = await getKernel("webgpu-kernels/com.microsoft.FusedConv", { version: 1 });
|
| 87 |
+
const { y } = await kernel({
|
| 88 |
+
x: { data: xData, shape: [1, 32, 8, 8] },
|
| 89 |
+
w: { data: wData, shape: [32, 32, 1, 1] },
|
| 90 |
+
});
|
| 91 |
+
```
|
build/webgpu/bench.json
ADDED
|
@@ -0,0 +1,922 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"op": "com.microsoft.FusedConv",
|
| 3 |
+
"tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
|
| 4 |
+
"cases": [
|
| 5 |
+
{
|
| 6 |
+
"name": "fusedconv-f32-b1c16m32-32x32-k3",
|
| 7 |
+
"preset": "smoke",
|
| 8 |
+
"vars": {
|
| 9 |
+
"batch": 1,
|
| 10 |
+
"inChannels": 16,
|
| 11 |
+
"outChannels": 32,
|
| 12 |
+
"inH": 32,
|
| 13 |
+
"inW": 32,
|
| 14 |
+
"kernelH": 3,
|
| 15 |
+
"kernelW": 3,
|
| 16 |
+
"strideH": 1,
|
| 17 |
+
"strideW": 1,
|
| 18 |
+
"padH": 1,
|
| 19 |
+
"padW": 1
|
| 20 |
+
},
|
| 21 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 22 |
+
"inputs": {
|
| 23 |
+
"x": { "shape": [1, 16, 32, 32], "dtype": "float32", "dist": "normal", "seed": 112, "scale": 0.2 },
|
| 24 |
+
"w": { "shape": [32, 16, 3, 3], "dtype": "float32", "dist": "normal", "seed": 113, "scale": 0.1 }
|
| 25 |
+
},
|
| 26 |
+
"outputs": { "y": { "shape": [1, 32, 32, 32], "dtype": "float32" } },
|
| 27 |
+
"bench": {
|
| 28 |
+
"primary": true,
|
| 29 |
+
"metrics": [
|
| 30 |
+
{
|
| 31 |
+
"type": "gflops",
|
| 32 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 33 |
+
}
|
| 34 |
+
]
|
| 35 |
+
}
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"name": "fusedconv-f32-bias-b1c16m32-32x32-k3",
|
| 39 |
+
"preset": "smoke",
|
| 40 |
+
"vars": {
|
| 41 |
+
"batch": 1,
|
| 42 |
+
"inChannels": 16,
|
| 43 |
+
"outChannels": 32,
|
| 44 |
+
"inH": 32,
|
| 45 |
+
"inW": 32,
|
| 46 |
+
"kernelH": 3,
|
| 47 |
+
"kernelW": 3,
|
| 48 |
+
"strideH": 1,
|
| 49 |
+
"strideW": 1,
|
| 50 |
+
"padH": 1,
|
| 51 |
+
"padW": 1
|
| 52 |
+
},
|
| 53 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 54 |
+
"inputs": {
|
| 55 |
+
"x": { "shape": [1, 16, 32, 32], "dtype": "float32", "dist": "normal", "seed": 113, "scale": 0.2 },
|
| 56 |
+
"w": { "shape": [32, 16, 3, 3], "dtype": "float32", "dist": "normal", "seed": 114, "scale": 0.1 },
|
| 57 |
+
"bias": { "shape": [32], "dtype": "float32", "dist": "normal", "seed": 115, "scale": 0.05 }
|
| 58 |
+
},
|
| 59 |
+
"outputs": { "y": { "shape": [1, 32, 32, 32], "dtype": "float32" } },
|
| 60 |
+
"bench": {
|
| 61 |
+
"primary": true,
|
| 62 |
+
"metrics": [
|
| 63 |
+
{
|
| 64 |
+
"type": "gflops",
|
| 65 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 66 |
+
}
|
| 67 |
+
]
|
| 68 |
+
}
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"name": "fused-flux2vae-dec-mid-512c-64x64-k3",
|
| 72 |
+
"preset": "model",
|
| 73 |
+
"vars": {
|
| 74 |
+
"batch": 1,
|
| 75 |
+
"inChannels": 512,
|
| 76 |
+
"outChannels": 512,
|
| 77 |
+
"inH": 64,
|
| 78 |
+
"inW": 64,
|
| 79 |
+
"kernelH": 3,
|
| 80 |
+
"kernelW": 3,
|
| 81 |
+
"strideH": 1,
|
| 82 |
+
"strideW": 1,
|
| 83 |
+
"padH": 1,
|
| 84 |
+
"padW": 1
|
| 85 |
+
},
|
| 86 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 87 |
+
"inputs": {
|
| 88 |
+
"x": { "shape": [1, 512, 64, 64], "dtype": "float32", "dist": "normal", "seed": 340, "scale": 0.2 },
|
| 89 |
+
"w": { "shape": [512, 512, 3, 3], "dtype": "float32", "dist": "normal", "seed": 341, "scale": 0.02 }
|
| 90 |
+
},
|
| 91 |
+
"outputs": { "y": { "shape": [1, 512, 64, 64], "dtype": "float32" } },
|
| 92 |
+
"bench": {
|
| 93 |
+
"metrics": [
|
| 94 |
+
{
|
| 95 |
+
"type": "gflops",
|
| 96 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 97 |
+
}
|
| 98 |
+
]
|
| 99 |
+
}
|
| 100 |
+
},
|
| 101 |
+
{
|
| 102 |
+
"name": "fused-flux2vae-dec-up1-512c-128x128-k3",
|
| 103 |
+
"preset": "model",
|
| 104 |
+
"vars": {
|
| 105 |
+
"batch": 1,
|
| 106 |
+
"inChannels": 512,
|
| 107 |
+
"outChannels": 512,
|
| 108 |
+
"inH": 128,
|
| 109 |
+
"inW": 128,
|
| 110 |
+
"kernelH": 3,
|
| 111 |
+
"kernelW": 3,
|
| 112 |
+
"strideH": 1,
|
| 113 |
+
"strideW": 1,
|
| 114 |
+
"padH": 1,
|
| 115 |
+
"padW": 1
|
| 116 |
+
},
|
| 117 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 118 |
+
"inputs": {
|
| 119 |
+
"x": { "shape": [1, 512, 128, 128], "dtype": "float32", "dist": "normal", "seed": 342, "scale": 0.2 },
|
| 120 |
+
"w": { "shape": [512, 512, 3, 3], "dtype": "float32", "dist": "normal", "seed": 343, "scale": 0.02 }
|
| 121 |
+
},
|
| 122 |
+
"outputs": { "y": { "shape": [1, 512, 128, 128], "dtype": "float32" } },
|
| 123 |
+
"bench": {
|
| 124 |
+
"metrics": [
|
| 125 |
+
{
|
| 126 |
+
"type": "gflops",
|
| 127 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 128 |
+
}
|
| 129 |
+
]
|
| 130 |
+
}
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"name": "fused-flux2vae-attn-proj-512c-64x64-k1",
|
| 134 |
+
"preset": "model",
|
| 135 |
+
"vars": {
|
| 136 |
+
"batch": 1,
|
| 137 |
+
"inChannels": 512,
|
| 138 |
+
"outChannels": 512,
|
| 139 |
+
"inH": 64,
|
| 140 |
+
"inW": 64,
|
| 141 |
+
"kernelH": 1,
|
| 142 |
+
"kernelW": 1,
|
| 143 |
+
"strideH": 1,
|
| 144 |
+
"strideW": 1,
|
| 145 |
+
"padH": 0,
|
| 146 |
+
"padW": 0
|
| 147 |
+
},
|
| 148 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 149 |
+
"inputs": {
|
| 150 |
+
"x": { "shape": [1, 512, 64, 64], "dtype": "float32", "dist": "normal", "seed": 344, "scale": 0.2 },
|
| 151 |
+
"w": { "shape": [512, 512, 1, 1], "dtype": "float32", "dist": "normal", "seed": 345, "scale": 0.02 }
|
| 152 |
+
},
|
| 153 |
+
"outputs": { "y": { "shape": [1, 512, 64, 64], "dtype": "float32" } },
|
| 154 |
+
"bench": {
|
| 155 |
+
"metrics": [
|
| 156 |
+
{
|
| 157 |
+
"type": "gflops",
|
| 158 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 159 |
+
}
|
| 160 |
+
]
|
| 161 |
+
}
|
| 162 |
+
},
|
| 163 |
+
{
|
| 164 |
+
"name": "fused-flux2vae-attn-proj-bias-512c-64x64-k1",
|
| 165 |
+
"preset": "model",
|
| 166 |
+
"vars": {
|
| 167 |
+
"batch": 1,
|
| 168 |
+
"inChannels": 512,
|
| 169 |
+
"outChannels": 512,
|
| 170 |
+
"inH": 64,
|
| 171 |
+
"inW": 64,
|
| 172 |
+
"kernelH": 1,
|
| 173 |
+
"kernelW": 1,
|
| 174 |
+
"strideH": 1,
|
| 175 |
+
"strideW": 1,
|
| 176 |
+
"padH": 0,
|
| 177 |
+
"padW": 0
|
| 178 |
+
},
|
| 179 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 180 |
+
"inputs": {
|
| 181 |
+
"x": { "shape": [1, 512, 64, 64], "dtype": "float32", "dist": "normal", "seed": 346, "scale": 0.2 },
|
| 182 |
+
"w": { "shape": [512, 512, 1, 1], "dtype": "float32", "dist": "normal", "seed": 347, "scale": 0.02 },
|
| 183 |
+
"bias": { "shape": [512], "dtype": "float32", "dist": "normal", "seed": 348, "scale": 0.05 }
|
| 184 |
+
},
|
| 185 |
+
"outputs": { "y": { "shape": [1, 512, 64, 64], "dtype": "float32" } },
|
| 186 |
+
"bench": {
|
| 187 |
+
"metrics": [
|
| 188 |
+
{
|
| 189 |
+
"type": "gflops",
|
| 190 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 191 |
+
}
|
| 192 |
+
]
|
| 193 |
+
}
|
| 194 |
+
},
|
| 195 |
+
{
|
| 196 |
+
"name": "fusedconv-f32-1x1-b1c256m256-32x32",
|
| 197 |
+
"preset": "smoke",
|
| 198 |
+
"vars": {
|
| 199 |
+
"batch": 1,
|
| 200 |
+
"inChannels": 256,
|
| 201 |
+
"outChannels": 256,
|
| 202 |
+
"inH": 32,
|
| 203 |
+
"inW": 32,
|
| 204 |
+
"kernelH": 1,
|
| 205 |
+
"kernelW": 1,
|
| 206 |
+
"strideH": 1,
|
| 207 |
+
"strideW": 1,
|
| 208 |
+
"padH": 0,
|
| 209 |
+
"padW": 0
|
| 210 |
+
},
|
| 211 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 212 |
+
"inputs": {
|
| 213 |
+
"x": { "shape": [1, 256, 32, 32], "dtype": "float32", "dist": "normal", "seed": 350, "scale": 0.2 },
|
| 214 |
+
"w": { "shape": [256, 256, 1, 1], "dtype": "float32", "dist": "normal", "seed": 351, "scale": 0.02 }
|
| 215 |
+
},
|
| 216 |
+
"outputs": { "y": { "shape": [1, 256, 32, 32], "dtype": "float32" } },
|
| 217 |
+
"bench": {
|
| 218 |
+
"metrics": [
|
| 219 |
+
{
|
| 220 |
+
"type": "gflops",
|
| 221 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 222 |
+
}
|
| 223 |
+
]
|
| 224 |
+
}
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
"name": "fusedconv-f32-1x1-bias-b1c256m256-32x32",
|
| 228 |
+
"preset": "smoke",
|
| 229 |
+
"provenance": {
|
| 230 |
+
"source": "authored for performance coverage",
|
| 231 |
+
"notes": "Bias twin of the paired non-bias case at the same 64 register tiles. No bias case sat in the 64-127 tile band, so the bias register-tile floor had no benchmark on either side of it."
|
| 232 |
+
},
|
| 233 |
+
"vars": {
|
| 234 |
+
"batch": 1,
|
| 235 |
+
"inChannels": 256,
|
| 236 |
+
"outChannels": 256,
|
| 237 |
+
"inH": 32,
|
| 238 |
+
"inW": 32,
|
| 239 |
+
"kernelH": 1,
|
| 240 |
+
"kernelW": 1,
|
| 241 |
+
"strideH": 1,
|
| 242 |
+
"strideW": 1,
|
| 243 |
+
"padH": 0,
|
| 244 |
+
"padW": 0
|
| 245 |
+
},
|
| 246 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 247 |
+
"inputs": {
|
| 248 |
+
"x": { "shape": [1, 256, 32, 32], "dtype": "float32", "dist": "normal", "seed": 3521, "scale": 0.2 },
|
| 249 |
+
"w": { "shape": [256, 256, 1, 1], "dtype": "float32", "dist": "normal", "seed": 3522, "scale": 0.02 },
|
| 250 |
+
"bias": { "shape": [256], "dtype": "float32", "dist": "normal", "seed": 3523, "scale": 0.05 }
|
| 251 |
+
},
|
| 252 |
+
"outputs": { "y": { "shape": [1, 256, 32, 32], "dtype": "float32" } },
|
| 253 |
+
"bench": {
|
| 254 |
+
"metrics": [
|
| 255 |
+
{
|
| 256 |
+
"type": "gflops",
|
| 257 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 258 |
+
}
|
| 259 |
+
]
|
| 260 |
+
}
|
| 261 |
+
},
|
| 262 |
+
{
|
| 263 |
+
"name": "fusedconv-f32-im2col-b1c32m64-32x32-k3",
|
| 264 |
+
"preset": "smoke",
|
| 265 |
+
"vars": {
|
| 266 |
+
"batch": 1,
|
| 267 |
+
"inChannels": 32,
|
| 268 |
+
"outChannels": 64,
|
| 269 |
+
"inH": 32,
|
| 270 |
+
"inW": 32,
|
| 271 |
+
"kernelH": 3,
|
| 272 |
+
"kernelW": 3,
|
| 273 |
+
"strideH": 1,
|
| 274 |
+
"strideW": 1,
|
| 275 |
+
"padH": 1,
|
| 276 |
+
"padW": 1
|
| 277 |
+
},
|
| 278 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 279 |
+
"inputs": {
|
| 280 |
+
"x": { "shape": [1, 32, 32, 32], "dtype": "float32", "dist": "normal", "seed": 352, "scale": 0.2 },
|
| 281 |
+
"w": { "shape": [64, 32, 3, 3], "dtype": "float32", "dist": "normal", "seed": 353, "scale": 0.02 }
|
| 282 |
+
},
|
| 283 |
+
"outputs": { "y": { "shape": [1, 64, 32, 32], "dtype": "float32" } },
|
| 284 |
+
"bench": {
|
| 285 |
+
"metrics": [
|
| 286 |
+
{
|
| 287 |
+
"type": "gflops",
|
| 288 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 289 |
+
}
|
| 290 |
+
]
|
| 291 |
+
}
|
| 292 |
+
},
|
| 293 |
+
{
|
| 294 |
+
"name": "fusedconv-f16-1x1-b1c512m512-64x64",
|
| 295 |
+
"preset": "model",
|
| 296 |
+
"vars": {
|
| 297 |
+
"batch": 1,
|
| 298 |
+
"inChannels": 512,
|
| 299 |
+
"outChannels": 512,
|
| 300 |
+
"inH": 64,
|
| 301 |
+
"inW": 64,
|
| 302 |
+
"kernelH": 1,
|
| 303 |
+
"kernelW": 1,
|
| 304 |
+
"strideH": 1,
|
| 305 |
+
"strideW": 1,
|
| 306 |
+
"padH": 0,
|
| 307 |
+
"padW": 0
|
| 308 |
+
},
|
| 309 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 310 |
+
"inputs": {
|
| 311 |
+
"x": { "shape": [1, 512, 64, 64], "dtype": "float16", "dist": "normal", "seed": 354, "scale": 0.2 },
|
| 312 |
+
"w": { "shape": [512, 512, 1, 1], "dtype": "float16", "dist": "normal", "seed": 355, "scale": 0.02 }
|
| 313 |
+
},
|
| 314 |
+
"outputs": { "y": { "shape": [1, 512, 64, 64], "dtype": "float16" } },
|
| 315 |
+
"bench": {
|
| 316 |
+
"metrics": [
|
| 317 |
+
{
|
| 318 |
+
"type": "gflops",
|
| 319 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 320 |
+
}
|
| 321 |
+
]
|
| 322 |
+
}
|
| 323 |
+
},
|
| 324 |
+
{
|
| 325 |
+
"name": "fusedconv-f16-b1c512m512-64x64-k3",
|
| 326 |
+
"preset": "model",
|
| 327 |
+
"vars": {
|
| 328 |
+
"batch": 1,
|
| 329 |
+
"inChannels": 512,
|
| 330 |
+
"outChannels": 512,
|
| 331 |
+
"inH": 64,
|
| 332 |
+
"inW": 64,
|
| 333 |
+
"kernelH": 3,
|
| 334 |
+
"kernelW": 3,
|
| 335 |
+
"strideH": 1,
|
| 336 |
+
"strideW": 1,
|
| 337 |
+
"padH": 1,
|
| 338 |
+
"padW": 1
|
| 339 |
+
},
|
| 340 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 341 |
+
"inputs": {
|
| 342 |
+
"x": { "shape": [1, 512, 64, 64], "dtype": "float16", "dist": "normal", "seed": 356, "scale": 0.2 },
|
| 343 |
+
"w": { "shape": [512, 512, 3, 3], "dtype": "float16", "dist": "normal", "seed": 357, "scale": 0.02 }
|
| 344 |
+
},
|
| 345 |
+
"outputs": { "y": { "shape": [1, 512, 64, 64], "dtype": "float16" } },
|
| 346 |
+
"bench": {
|
| 347 |
+
"metrics": [
|
| 348 |
+
{
|
| 349 |
+
"type": "gflops",
|
| 350 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 351 |
+
}
|
| 352 |
+
]
|
| 353 |
+
}
|
| 354 |
+
},
|
| 355 |
+
{
|
| 356 |
+
"name": "fusedconv-f32-b1c64m128-56x56-k3s2",
|
| 357 |
+
"preset": "model",
|
| 358 |
+
"vars": {
|
| 359 |
+
"batch": 1,
|
| 360 |
+
"inChannels": 64,
|
| 361 |
+
"outChannels": 128,
|
| 362 |
+
"inH": 56,
|
| 363 |
+
"inW": 56,
|
| 364 |
+
"kernelH": 3,
|
| 365 |
+
"kernelW": 3,
|
| 366 |
+
"strideH": 2,
|
| 367 |
+
"strideW": 2,
|
| 368 |
+
"padH": 1,
|
| 369 |
+
"padW": 1
|
| 370 |
+
},
|
| 371 |
+
"attrs": { "activation": "Relu", "strides": [2, 2], "pads": [1, 1, 1, 1] },
|
| 372 |
+
"inputs": {
|
| 373 |
+
"x": { "shape": [1, 64, 56, 56], "dtype": "float32", "dist": "normal", "seed": 358, "scale": 0.2 },
|
| 374 |
+
"w": { "shape": [128, 64, 3, 3], "dtype": "float32", "dist": "normal", "seed": 359, "scale": 0.02 }
|
| 375 |
+
},
|
| 376 |
+
"outputs": { "y": { "shape": [1, 128, 28, 28], "dtype": "float32" } },
|
| 377 |
+
"bench": {
|
| 378 |
+
"metrics": [
|
| 379 |
+
{
|
| 380 |
+
"type": "gflops",
|
| 381 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 382 |
+
}
|
| 383 |
+
]
|
| 384 |
+
}
|
| 385 |
+
},
|
| 386 |
+
{
|
| 387 |
+
"name": "fusedconv-f32-group4-b1c64m64-32x32-k3",
|
| 388 |
+
"preset": "model",
|
| 389 |
+
"vars": {
|
| 390 |
+
"batch": 1,
|
| 391 |
+
"inChannels": 64,
|
| 392 |
+
"outChannels": 64,
|
| 393 |
+
"inH": 32,
|
| 394 |
+
"inW": 32,
|
| 395 |
+
"kernelH": 3,
|
| 396 |
+
"kernelW": 3,
|
| 397 |
+
"strideH": 1,
|
| 398 |
+
"strideW": 1,
|
| 399 |
+
"padH": 1,
|
| 400 |
+
"padW": 1
|
| 401 |
+
},
|
| 402 |
+
"attrs": { "group": 4, "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 403 |
+
"inputs": {
|
| 404 |
+
"x": { "shape": [1, 64, 32, 32], "dtype": "float32", "dist": "normal", "seed": 360, "scale": 0.2 },
|
| 405 |
+
"w": { "shape": [64, 16, 3, 3], "dtype": "float32", "dist": "normal", "seed": 361, "scale": 0.02 }
|
| 406 |
+
},
|
| 407 |
+
"outputs": { "y": { "shape": [1, 64, 32, 32], "dtype": "float32" } },
|
| 408 |
+
"bench": {
|
| 409 |
+
"metrics": [
|
| 410 |
+
{
|
| 411 |
+
"type": "gflops",
|
| 412 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 413 |
+
}
|
| 414 |
+
]
|
| 415 |
+
}
|
| 416 |
+
},
|
| 417 |
+
{
|
| 418 |
+
"name": "fusedconv-f32-stem-3ch-224-k7s2",
|
| 419 |
+
"preset": "model",
|
| 420 |
+
"vars": {
|
| 421 |
+
"batch": 1,
|
| 422 |
+
"inChannels": 3,
|
| 423 |
+
"outChannels": 64,
|
| 424 |
+
"inH": 224,
|
| 425 |
+
"inW": 224,
|
| 426 |
+
"kernelH": 7,
|
| 427 |
+
"kernelW": 7,
|
| 428 |
+
"strideH": 2,
|
| 429 |
+
"strideW": 2,
|
| 430 |
+
"padH": 3,
|
| 431 |
+
"padW": 3
|
| 432 |
+
},
|
| 433 |
+
"attrs": { "activation": "Relu", "strides": [2, 2], "pads": [3, 3, 3, 3] },
|
| 434 |
+
"inputs": {
|
| 435 |
+
"x": { "shape": [1, 3, 224, 224], "dtype": "float32", "dist": "normal", "seed": 1458, "scale": 0.2 },
|
| 436 |
+
"w": { "shape": [64, 3, 7, 7], "dtype": "float32", "dist": "normal", "seed": 1459, "scale": 0.02 }
|
| 437 |
+
},
|
| 438 |
+
"outputs": { "y": { "shape": [1, 64, 112, 112], "dtype": "float32" } },
|
| 439 |
+
"bench": {
|
| 440 |
+
"metrics": [
|
| 441 |
+
{
|
| 442 |
+
"type": "gflops",
|
| 443 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 444 |
+
}
|
| 445 |
+
]
|
| 446 |
+
}
|
| 447 |
+
},
|
| 448 |
+
{
|
| 449 |
+
"name": "fusedconv-f32-resnet-stage-256c-14x14-k3",
|
| 450 |
+
"preset": "model",
|
| 451 |
+
"vars": {
|
| 452 |
+
"batch": 1,
|
| 453 |
+
"inChannels": 256,
|
| 454 |
+
"outChannels": 256,
|
| 455 |
+
"inH": 14,
|
| 456 |
+
"inW": 14,
|
| 457 |
+
"kernelH": 3,
|
| 458 |
+
"kernelW": 3,
|
| 459 |
+
"strideH": 1,
|
| 460 |
+
"strideW": 1,
|
| 461 |
+
"padH": 1,
|
| 462 |
+
"padW": 1
|
| 463 |
+
},
|
| 464 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 465 |
+
"inputs": {
|
| 466 |
+
"x": { "shape": [1, 256, 14, 14], "dtype": "float32", "dist": "normal", "seed": 2458, "scale": 0.2 },
|
| 467 |
+
"w": { "shape": [256, 256, 3, 3], "dtype": "float32", "dist": "normal", "seed": 2459, "scale": 0.02 }
|
| 468 |
+
},
|
| 469 |
+
"outputs": { "y": { "shape": [1, 256, 14, 14], "dtype": "float32" } },
|
| 470 |
+
"bench": {
|
| 471 |
+
"metrics": [
|
| 472 |
+
{
|
| 473 |
+
"type": "gflops",
|
| 474 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 475 |
+
}
|
| 476 |
+
]
|
| 477 |
+
}
|
| 478 |
+
},
|
| 479 |
+
{
|
| 480 |
+
"name": "fusedconv-1x1-tinyN-c256m256-8x8",
|
| 481 |
+
"preset": "smoke",
|
| 482 |
+
"vars": {
|
| 483 |
+
"batch": 1,
|
| 484 |
+
"inChannels": 256,
|
| 485 |
+
"outChannels": 256,
|
| 486 |
+
"inH": 8,
|
| 487 |
+
"inW": 8,
|
| 488 |
+
"kernelH": 1,
|
| 489 |
+
"kernelW": 1,
|
| 490 |
+
"strideH": 1,
|
| 491 |
+
"strideW": 1,
|
| 492 |
+
"padH": 0,
|
| 493 |
+
"padW": 0
|
| 494 |
+
},
|
| 495 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 496 |
+
"inputs": {
|
| 497 |
+
"x": { "shape": [1, 256, 8, 8], "dtype": "float32", "dist": "normal", "seed": 951, "scale": 0.2 },
|
| 498 |
+
"w": { "shape": [256, 256, 1, 1], "dtype": "float32", "dist": "normal", "seed": 1642, "scale": 0.05 }
|
| 499 |
+
},
|
| 500 |
+
"outputs": { "y": { "shape": [1, 256, 8, 8], "dtype": "float32" } },
|
| 501 |
+
"bench": {
|
| 502 |
+
"primary": true,
|
| 503 |
+
"metrics": [
|
| 504 |
+
{
|
| 505 |
+
"type": "gflops",
|
| 506 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 507 |
+
}
|
| 508 |
+
]
|
| 509 |
+
}
|
| 510 |
+
},
|
| 511 |
+
{
|
| 512 |
+
"name": "fusedconv-1x1-bigN-c256m256-64x64",
|
| 513 |
+
"preset": "smoke",
|
| 514 |
+
"vars": {
|
| 515 |
+
"batch": 1,
|
| 516 |
+
"inChannels": 256,
|
| 517 |
+
"outChannels": 256,
|
| 518 |
+
"inH": 64,
|
| 519 |
+
"inW": 64,
|
| 520 |
+
"kernelH": 1,
|
| 521 |
+
"kernelW": 1,
|
| 522 |
+
"strideH": 1,
|
| 523 |
+
"strideW": 1,
|
| 524 |
+
"padH": 0,
|
| 525 |
+
"padW": 0
|
| 526 |
+
},
|
| 527 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 528 |
+
"inputs": {
|
| 529 |
+
"x": { "shape": [1, 256, 64, 64], "dtype": "float32", "dist": "normal", "seed": 7277, "scale": 0.2 },
|
| 530 |
+
"w": { "shape": [256, 256, 1, 1], "dtype": "float32", "dist": "normal", "seed": 1530, "scale": 0.05 }
|
| 531 |
+
},
|
| 532 |
+
"outputs": { "y": { "shape": [1, 256, 64, 64], "dtype": "float32" } },
|
| 533 |
+
"bench": {
|
| 534 |
+
"metrics": [
|
| 535 |
+
{
|
| 536 |
+
"type": "gflops",
|
| 537 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 538 |
+
}
|
| 539 |
+
]
|
| 540 |
+
}
|
| 541 |
+
},
|
| 542 |
+
{
|
| 543 |
+
"name": "fusedconv-3x3-unalignedK-c48m64-32x32",
|
| 544 |
+
"preset": "smoke",
|
| 545 |
+
"vars": {
|
| 546 |
+
"batch": 1,
|
| 547 |
+
"inChannels": 48,
|
| 548 |
+
"outChannels": 64,
|
| 549 |
+
"inH": 32,
|
| 550 |
+
"inW": 32,
|
| 551 |
+
"kernelH": 3,
|
| 552 |
+
"kernelW": 3,
|
| 553 |
+
"strideH": 1,
|
| 554 |
+
"strideW": 1,
|
| 555 |
+
"padH": 1,
|
| 556 |
+
"padW": 1
|
| 557 |
+
},
|
| 558 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 559 |
+
"inputs": {
|
| 560 |
+
"x": { "shape": [1, 48, 32, 32], "dtype": "float32", "dist": "normal", "seed": 3152, "scale": 0.2 },
|
| 561 |
+
"w": { "shape": [64, 48, 3, 3], "dtype": "float32", "dist": "normal", "seed": 7809, "scale": 0.05 }
|
| 562 |
+
},
|
| 563 |
+
"outputs": { "y": { "shape": [1, 64, 32, 32], "dtype": "float32" } },
|
| 564 |
+
"bench": {
|
| 565 |
+
"metrics": [
|
| 566 |
+
{
|
| 567 |
+
"type": "gflops",
|
| 568 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 569 |
+
}
|
| 570 |
+
]
|
| 571 |
+
}
|
| 572 |
+
},
|
| 573 |
+
{
|
| 574 |
+
"name": "fusedconv-3x3-alignedK-c64m64-32x32",
|
| 575 |
+
"preset": "smoke",
|
| 576 |
+
"vars": {
|
| 577 |
+
"batch": 1,
|
| 578 |
+
"inChannels": 64,
|
| 579 |
+
"outChannels": 64,
|
| 580 |
+
"inH": 32,
|
| 581 |
+
"inW": 32,
|
| 582 |
+
"kernelH": 3,
|
| 583 |
+
"kernelW": 3,
|
| 584 |
+
"strideH": 1,
|
| 585 |
+
"strideW": 1,
|
| 586 |
+
"padH": 1,
|
| 587 |
+
"padW": 1
|
| 588 |
+
},
|
| 589 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 590 |
+
"inputs": {
|
| 591 |
+
"x": { "shape": [1, 64, 32, 32], "dtype": "float32", "dist": "normal", "seed": 7770, "scale": 0.2 },
|
| 592 |
+
"w": { "shape": [64, 64, 3, 3], "dtype": "float32", "dist": "normal", "seed": 2676, "scale": 0.05 }
|
| 593 |
+
},
|
| 594 |
+
"outputs": { "y": { "shape": [1, 64, 32, 32], "dtype": "float32" } },
|
| 595 |
+
"bench": {
|
| 596 |
+
"metrics": [
|
| 597 |
+
{
|
| 598 |
+
"type": "gflops",
|
| 599 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 600 |
+
}
|
| 601 |
+
]
|
| 602 |
+
}
|
| 603 |
+
},
|
| 604 |
+
{
|
| 605 |
+
"name": "fusedconv-3x3-depthwise-c256-32x32",
|
| 606 |
+
"preset": "smoke",
|
| 607 |
+
"vars": {
|
| 608 |
+
"batch": 1,
|
| 609 |
+
"inChannels": 256,
|
| 610 |
+
"outChannels": 256,
|
| 611 |
+
"inH": 32,
|
| 612 |
+
"inW": 32,
|
| 613 |
+
"kernelH": 3,
|
| 614 |
+
"kernelW": 3,
|
| 615 |
+
"strideH": 1,
|
| 616 |
+
"strideW": 1,
|
| 617 |
+
"padH": 1,
|
| 618 |
+
"padW": 1
|
| 619 |
+
},
|
| 620 |
+
"attrs": { "group": 256, "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 621 |
+
"inputs": {
|
| 622 |
+
"x": { "shape": [1, 256, 32, 32], "dtype": "float32", "dist": "normal", "seed": 8532, "scale": 0.2 },
|
| 623 |
+
"w": { "shape": [256, 1, 3, 3], "dtype": "float32", "dist": "normal", "seed": 7192, "scale": 0.05 }
|
| 624 |
+
},
|
| 625 |
+
"outputs": { "y": { "shape": [1, 256, 32, 32], "dtype": "float32" } },
|
| 626 |
+
"bench": {
|
| 627 |
+
"metrics": [
|
| 628 |
+
{
|
| 629 |
+
"type": "gflops",
|
| 630 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 631 |
+
}
|
| 632 |
+
]
|
| 633 |
+
}
|
| 634 |
+
},
|
| 635 |
+
{
|
| 636 |
+
"name": "fusedconv-3x3-dense-c256m256-32x32",
|
| 637 |
+
"preset": "smoke",
|
| 638 |
+
"vars": {
|
| 639 |
+
"batch": 1,
|
| 640 |
+
"inChannels": 256,
|
| 641 |
+
"outChannels": 256,
|
| 642 |
+
"inH": 32,
|
| 643 |
+
"inW": 32,
|
| 644 |
+
"kernelH": 3,
|
| 645 |
+
"kernelW": 3,
|
| 646 |
+
"strideH": 1,
|
| 647 |
+
"strideW": 1,
|
| 648 |
+
"padH": 1,
|
| 649 |
+
"padW": 1
|
| 650 |
+
},
|
| 651 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 652 |
+
"inputs": {
|
| 653 |
+
"x": { "shape": [1, 256, 32, 32], "dtype": "float32", "dist": "normal", "seed": 839, "scale": 0.2 },
|
| 654 |
+
"w": { "shape": [256, 256, 3, 3], "dtype": "float32", "dist": "normal", "seed": 1375, "scale": 0.05 }
|
| 655 |
+
},
|
| 656 |
+
"outputs": { "y": { "shape": [1, 256, 32, 32], "dtype": "float32" } },
|
| 657 |
+
"bench": {
|
| 658 |
+
"metrics": [
|
| 659 |
+
{
|
| 660 |
+
"type": "gflops",
|
| 661 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 662 |
+
}
|
| 663 |
+
]
|
| 664 |
+
}
|
| 665 |
+
},
|
| 666 |
+
{
|
| 667 |
+
"name": "fusedconv-3x3-dense-bias-c256m256-32x32",
|
| 668 |
+
"preset": "smoke",
|
| 669 |
+
"provenance": {
|
| 670 |
+
"source": "authored for performance coverage",
|
| 671 |
+
"notes": "Bias twin of the paired non-bias case at the same 64 register tiles. No bias case sat in the 64-127 tile band, so the bias register-tile floor had no benchmark on either side of it."
|
| 672 |
+
},
|
| 673 |
+
"vars": {
|
| 674 |
+
"batch": 1,
|
| 675 |
+
"inChannels": 256,
|
| 676 |
+
"outChannels": 256,
|
| 677 |
+
"inH": 32,
|
| 678 |
+
"inW": 32,
|
| 679 |
+
"kernelH": 3,
|
| 680 |
+
"kernelW": 3,
|
| 681 |
+
"strideH": 1,
|
| 682 |
+
"strideW": 1,
|
| 683 |
+
"padH": 1,
|
| 684 |
+
"padW": 1
|
| 685 |
+
},
|
| 686 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 687 |
+
"inputs": {
|
| 688 |
+
"x": { "shape": [1, 256, 32, 32], "dtype": "float32", "dist": "normal", "seed": 3531, "scale": 0.2 },
|
| 689 |
+
"w": { "shape": [256, 256, 3, 3], "dtype": "float32", "dist": "normal", "seed": 3532, "scale": 0.05 },
|
| 690 |
+
"bias": { "shape": [256], "dtype": "float32", "dist": "normal", "seed": 3533, "scale": 0.05 }
|
| 691 |
+
},
|
| 692 |
+
"outputs": { "y": { "shape": [1, 256, 32, 32], "dtype": "float32" } },
|
| 693 |
+
"bench": {
|
| 694 |
+
"metrics": [
|
| 695 |
+
{
|
| 696 |
+
"type": "gflops",
|
| 697 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 698 |
+
}
|
| 699 |
+
]
|
| 700 |
+
}
|
| 701 |
+
},
|
| 702 |
+
{
|
| 703 |
+
"name": "fusedconv-stem-cin3-k7s2-224",
|
| 704 |
+
"preset": "smoke",
|
| 705 |
+
"vars": {
|
| 706 |
+
"batch": 1,
|
| 707 |
+
"inChannels": 3,
|
| 708 |
+
"outChannels": 64,
|
| 709 |
+
"inH": 224,
|
| 710 |
+
"inW": 224,
|
| 711 |
+
"kernelH": 7,
|
| 712 |
+
"kernelW": 7,
|
| 713 |
+
"strideH": 2,
|
| 714 |
+
"strideW": 2,
|
| 715 |
+
"padH": 3,
|
| 716 |
+
"padW": 3
|
| 717 |
+
},
|
| 718 |
+
"attrs": { "activation": "Relu", "strides": [2, 2], "pads": [3, 3, 3, 3] },
|
| 719 |
+
"inputs": {
|
| 720 |
+
"x": { "shape": [1, 3, 224, 224], "dtype": "float32", "dist": "normal", "seed": 1633, "scale": 0.2 },
|
| 721 |
+
"w": { "shape": [64, 3, 7, 7], "dtype": "float32", "dist": "normal", "seed": 3717, "scale": 0.05 }
|
| 722 |
+
},
|
| 723 |
+
"outputs": { "y": { "shape": [1, 64, 112, 112], "dtype": "float32" } },
|
| 724 |
+
"bench": {
|
| 725 |
+
"metrics": [
|
| 726 |
+
{
|
| 727 |
+
"type": "gflops",
|
| 728 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 729 |
+
}
|
| 730 |
+
]
|
| 731 |
+
}
|
| 732 |
+
},
|
| 733 |
+
{
|
| 734 |
+
"name": "fusedconv-mid-cin32-k3-112",
|
| 735 |
+
"preset": "smoke",
|
| 736 |
+
"vars": {
|
| 737 |
+
"batch": 1,
|
| 738 |
+
"inChannels": 32,
|
| 739 |
+
"outChannels": 64,
|
| 740 |
+
"inH": 112,
|
| 741 |
+
"inW": 112,
|
| 742 |
+
"kernelH": 3,
|
| 743 |
+
"kernelW": 3,
|
| 744 |
+
"strideH": 1,
|
| 745 |
+
"strideW": 1,
|
| 746 |
+
"padH": 1,
|
| 747 |
+
"padW": 1
|
| 748 |
+
},
|
| 749 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [1, 1, 1, 1] },
|
| 750 |
+
"inputs": {
|
| 751 |
+
"x": { "shape": [1, 32, 112, 112], "dtype": "float32", "dist": "normal", "seed": 895, "scale": 0.2 },
|
| 752 |
+
"w": { "shape": [64, 32, 3, 3], "dtype": "float32", "dist": "normal", "seed": 3583, "scale": 0.05 }
|
| 753 |
+
},
|
| 754 |
+
"outputs": { "y": { "shape": [1, 64, 112, 112], "dtype": "float32" } },
|
| 755 |
+
"bench": {
|
| 756 |
+
"metrics": [
|
| 757 |
+
{
|
| 758 |
+
"type": "gflops",
|
| 759 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 760 |
+
}
|
| 761 |
+
]
|
| 762 |
+
}
|
| 763 |
+
},
|
| 764 |
+
{
|
| 765 |
+
"name": "fusedconv-1x1-oddM66-c64-32x32",
|
| 766 |
+
"preset": "smoke",
|
| 767 |
+
"vars": {
|
| 768 |
+
"batch": 1,
|
| 769 |
+
"inChannels": 64,
|
| 770 |
+
"outChannels": 66,
|
| 771 |
+
"inH": 32,
|
| 772 |
+
"inW": 32,
|
| 773 |
+
"kernelH": 1,
|
| 774 |
+
"kernelW": 1,
|
| 775 |
+
"strideH": 1,
|
| 776 |
+
"strideW": 1,
|
| 777 |
+
"padH": 0,
|
| 778 |
+
"padW": 0
|
| 779 |
+
},
|
| 780 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 781 |
+
"inputs": {
|
| 782 |
+
"x": { "shape": [1, 64, 32, 32], "dtype": "float32", "dist": "normal", "seed": 7607, "scale": 0.2 },
|
| 783 |
+
"w": { "shape": [66, 64, 1, 1], "dtype": "float32", "dist": "normal", "seed": 4643, "scale": 0.05 }
|
| 784 |
+
},
|
| 785 |
+
"outputs": { "y": { "shape": [1, 66, 32, 32], "dtype": "float32" } },
|
| 786 |
+
"bench": {
|
| 787 |
+
"metrics": [
|
| 788 |
+
{
|
| 789 |
+
"type": "gflops",
|
| 790 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 791 |
+
}
|
| 792 |
+
]
|
| 793 |
+
}
|
| 794 |
+
},
|
| 795 |
+
{
|
| 796 |
+
"name": "fusedconv-1x1-pow2M64-c64-32x32",
|
| 797 |
+
"preset": "smoke",
|
| 798 |
+
"vars": {
|
| 799 |
+
"batch": 1,
|
| 800 |
+
"inChannels": 64,
|
| 801 |
+
"outChannels": 64,
|
| 802 |
+
"inH": 32,
|
| 803 |
+
"inW": 32,
|
| 804 |
+
"kernelH": 1,
|
| 805 |
+
"kernelW": 1,
|
| 806 |
+
"strideH": 1,
|
| 807 |
+
"strideW": 1,
|
| 808 |
+
"padH": 0,
|
| 809 |
+
"padW": 0
|
| 810 |
+
},
|
| 811 |
+
"attrs": { "activation": "Relu", "strides": [1, 1], "pads": [0, 0, 0, 0] },
|
| 812 |
+
"inputs": {
|
| 813 |
+
"x": { "shape": [1, 64, 32, 32], "dtype": "float32", "dist": "normal", "seed": 8186, "scale": 0.2 },
|
| 814 |
+
"w": { "shape": [64, 64, 1, 1], "dtype": "float32", "dist": "normal", "seed": 7554, "scale": 0.05 }
|
| 815 |
+
},
|
| 816 |
+
"outputs": { "y": { "shape": [1, 64, 32, 32], "dtype": "float32" } },
|
| 817 |
+
"bench": {
|
| 818 |
+
"metrics": [
|
| 819 |
+
{
|
| 820 |
+
"type": "gflops",
|
| 821 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 822 |
+
}
|
| 823 |
+
]
|
| 824 |
+
}
|
| 825 |
+
},
|
| 826 |
+
{
|
| 827 |
+
"name": "fusedconv-group2-k7-c64m64-64x64-direct-fallback",
|
| 828 |
+
"provenance": {
|
| 829 |
+
"notes": "Named for the direct_unrolled route it was authored to expose. It now selects grouped_large_kernel_w4, the wide-output grouped kernel FusedConv gained from ai.onnx.Conv; kept under its original name as the grouped route regression guard."
|
| 830 |
+
},
|
| 831 |
+
"preset": "stress",
|
| 832 |
+
"vars": {
|
| 833 |
+
"batch": 1,
|
| 834 |
+
"inChannels": 64,
|
| 835 |
+
"outChannels": 64,
|
| 836 |
+
"inH": 64,
|
| 837 |
+
"inW": 64,
|
| 838 |
+
"kernelH": 7,
|
| 839 |
+
"kernelW": 7,
|
| 840 |
+
"strideH": 1,
|
| 841 |
+
"strideW": 1,
|
| 842 |
+
"padH": 3,
|
| 843 |
+
"padW": 3,
|
| 844 |
+
"group": 2
|
| 845 |
+
},
|
| 846 |
+
"attrs": { "group": 2, "activation": "Relu", "strides": [1, 1], "pads": [3, 3, 3, 3] },
|
| 847 |
+
"inputs": {
|
| 848 |
+
"x": { "shape": [1, 64, 64, 64], "dtype": "float32", "dist": "normal", "seed": 4801, "scale": 0.2 },
|
| 849 |
+
"w": { "shape": [64, 32, 7, 7], "dtype": "float32", "dist": "normal", "seed": 4802, "scale": 0.02 }
|
| 850 |
+
},
|
| 851 |
+
"outputs": { "y": { "shape": [1, 64, 64, 64], "dtype": "float32" } },
|
| 852 |
+
"bench": {
|
| 853 |
+
"metrics": [
|
| 854 |
+
{
|
| 855 |
+
"type": "gflops",
|
| 856 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 857 |
+
}
|
| 858 |
+
]
|
| 859 |
+
}
|
| 860 |
+
},
|
| 861 |
+
{
|
| 862 |
+
"name": "fusedconv-group4-k5-c128m128-48x48-direct-fallback",
|
| 863 |
+
"provenance": {
|
| 864 |
+
"notes": "Named for the direct_unrolled route it was authored to expose. It now selects grouped_large_kernel_w4, and its 5x5 kernel also sits in the band that kernel refused before its kernel-size floor was measured."
|
| 865 |
+
},
|
| 866 |
+
"preset": "stress",
|
| 867 |
+
"vars": {
|
| 868 |
+
"batch": 1,
|
| 869 |
+
"inChannels": 128,
|
| 870 |
+
"outChannels": 128,
|
| 871 |
+
"inH": 48,
|
| 872 |
+
"inW": 48,
|
| 873 |
+
"kernelH": 5,
|
| 874 |
+
"kernelW": 5,
|
| 875 |
+
"strideH": 1,
|
| 876 |
+
"strideW": 1,
|
| 877 |
+
"padH": 2,
|
| 878 |
+
"padW": 2,
|
| 879 |
+
"group": 4
|
| 880 |
+
},
|
| 881 |
+
"attrs": { "group": 4, "activation": "Relu", "strides": [1, 1], "pads": [2, 2, 2, 2] },
|
| 882 |
+
"inputs": {
|
| 883 |
+
"x": { "shape": [1, 128, 48, 48], "dtype": "float32", "dist": "normal", "seed": 4811, "scale": 0.2 },
|
| 884 |
+
"w": { "shape": [128, 32, 5, 5], "dtype": "float32", "dist": "normal", "seed": 4812, "scale": 0.02 }
|
| 885 |
+
},
|
| 886 |
+
"outputs": { "y": { "shape": [1, 128, 48, 48], "dtype": "float32" } },
|
| 887 |
+
"bench": {
|
| 888 |
+
"metrics": [
|
| 889 |
+
{
|
| 890 |
+
"type": "gflops",
|
| 891 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.w, 1) * args.kernelH * args.kernelW"
|
| 892 |
+
}
|
| 893 |
+
]
|
| 894 |
+
}
|
| 895 |
+
},
|
| 896 |
+
{
|
| 897 |
+
"name": "fusedconv-conv1d-hardswish-b1c80m512-w3000-k3s1",
|
| 898 |
+
"preset": "stress",
|
| 899 |
+
"provenance": {
|
| 900 |
+
"source": "ONNX Runtime FusedConv provider semantics",
|
| 901 |
+
"notes": "Large aligned temporal convolution with bias and provider-backed HardSwish on the register-tiled implicit-GEMM path."
|
| 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] },
|
| 905 |
+
"inputs": {
|
| 906 |
+
"x": { "shape": [1, 80, 3000], "dtype": "float32", "dist": "normal", "seed": 481, "scale": 0.2 },
|
| 907 |
+
"w": { "shape": [512, 80, 3], "dtype": "float32", "dist": "normal", "seed": 482, "scale": 0.1 },
|
| 908 |
+
"bias": { "shape": [512], "dtype": "float32", "dist": "normal", "seed": 483, "scale": 0.05 }
|
| 909 |
+
},
|
| 910 |
+
"outputs": { "y": { "shape": [1, 512, 3000], "dtype": "float32" } },
|
| 911 |
+
"bench": {
|
| 912 |
+
"primary": true,
|
| 913 |
+
"metrics": [
|
| 914 |
+
{
|
| 915 |
+
"type": "gflops",
|
| 916 |
+
"value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * args.inChannels * args.kernelW"
|
| 917 |
+
}
|
| 918 |
+
]
|
| 919 |
+
}
|
| 920 |
+
}
|
| 921 |
+
]
|
| 922 |
+
}
|
build/webgpu/conv-1x1-gemm-tiled-reg.wgsl.jinja
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// 1x1 / im2col Conv routed as a batched GEMM:
|
| 2 |
+
// Y[b] (M x N) = W (M x K) * X[b] (K x N), with M = outChannels,
|
| 3 |
+
// K = inChannels * kh * kw after im2col, and N = H * W. Register-blocked
|
| 4 |
+
// workgroup lanes compute TM x TN micro-tiles. The defaults use 16x16 lanes with
|
| 5 |
+
// an 8x4 micro-tile over a 64x64 output tile. Weight A is shared across the batch;
|
| 6 |
+
// input B and the output use the dispatch.z batch offset. Accumulation is f32;
|
| 7 |
+
// the optional epilogue applies bias, residual Z, and activation in the
|
| 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 |
+
//
|
| 12 |
+
// Both shared tiles are stored as vec4 groups along the axis each lane consumes
|
| 13 |
+
// contiguously: tileA groups four K values for one output row, tileB groups four
|
| 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 |
+
fn fused_act(v: f32) -> f32 {
|
| 26 |
+
{% if activation == "Relu" %}
|
| 27 |
+
return max(v, 0.0);
|
| 28 |
+
{% elif activation == "Clip" %}
|
| 29 |
+
return clamp(v, f32({{ actAlpha }}), f32({{ actBeta }}));
|
| 30 |
+
{% elif activation == "LeakyRelu" %}
|
| 31 |
+
return select(v * f32({{ actAlpha }}), v, v >= 0.0);
|
| 32 |
+
{% elif activation == "Sigmoid" %}
|
| 33 |
+
return 1.0 / (1.0 + exp(-v));
|
| 34 |
+
{% elif activation == "Tanh" %}
|
| 35 |
+
return tanh(v);
|
| 36 |
+
{% elif activation == "HardSigmoid" %}
|
| 37 |
+
return clamp(f32({{ actAlpha }}) * v + f32({{ actBeta }}), 0.0, 1.0);
|
| 38 |
+
{% else %}
|
| 39 |
+
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 40 |
+
{% endif %}
|
| 41 |
+
}
|
| 42 |
+
{% endif %}
|
| 43 |
+
|
| 44 |
+
{% set tileT = "f16" if usesF16 else "f32" -%}
|
| 45 |
+
{%- set GEMM_BK = gemmKTile if gemmKTile is defined else 16 %}
|
| 46 |
+
{% set GEMM_TM = gemmThreadRows if gemmThreadRows is defined else 8 %}
|
| 47 |
+
{% set GEMM_WG_X = gemmWorkgroupX if gemmWorkgroupX is defined else 16 %}
|
| 48 |
+
{% set GEMM_BM = gemmMTile if gemmMTile is defined else 64 %}
|
| 49 |
+
{% set GEMM_BN = gemmNTile if gemmNTile is defined else GEMM_WG_X * 4 %}
|
| 50 |
+
{% set GEMM_TN = gemmThreadColumns if gemmThreadColumns is defined else (GEMM_BN / GEMM_WG_X)|int %}
|
| 51 |
+
{% set GEMM_WG_Y = gemmWorkgroupY if gemmWorkgroupY is defined else (GEMM_BM / GEMM_TM)|int %}
|
| 52 |
+
{% set components = ["x", "y", "z", "w"] %}
|
| 53 |
+
const BK: u32 = {{ GEMM_BK }}u;
|
| 54 |
+
const BM: u32 = {{ GEMM_BM }}u;
|
| 55 |
+
const BN: u32 = {{ GEMM_BN }}u;
|
| 56 |
+
const TM: u32 = {{ GEMM_TM }}u;
|
| 57 |
+
const TN: u32 = {{ GEMM_TN }}u;
|
| 58 |
+
const WG_X: u32 = {{ GEMM_WG_X }}u;
|
| 59 |
+
const WG_SIZE: u32 = {{ GEMM_WG_X * GEMM_WG_Y }}u;
|
| 60 |
+
// Vector words per tile row: A groups K, B groups N.
|
| 61 |
+
const AK_VECS: u32 = BK / 4u;
|
| 62 |
+
const BN_VECS: u32 = BN / 4u;
|
| 63 |
+
|
| 64 |
+
// implicitIm2col leaves B unmaterialized. While staging a tile, the kernel
|
| 65 |
+
// decodes each (k, n) column-matrix coordinate into a raw NCHW input address.
|
| 66 |
+
// Overlapping windows may reread input values, trading address arithmetic and
|
| 67 |
+
// cache traffic for the storage and bandwidth of the expanded column matrix.
|
| 68 |
+
{% set implicitIm2col = implicitIm2col is defined and implicitIm2col %}
|
| 69 |
+
{% set fusedNarrowProjection = fusedNarrowProjection is defined and fusedNarrowProjection %}
|
| 70 |
+
{% if fusedNarrowProjection %}
|
| 71 |
+
{% set projectionChannels = projectionOutChannels %}
|
| 72 |
+
{% set projectionInputAct = inputActivation %}
|
| 73 |
+
{% set projectionOutputAct = outputActivation %}
|
| 74 |
+
{% set projectionScaled = hasOutputScale %}
|
| 75 |
+
{% set projectionBiased = hasProjectionBias %}
|
| 76 |
+
{% endif %}
|
| 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 gemmKLimit = "kEnd" if splitKPartial else "K" %}
|
| 81 |
+
{% set implicitGatherMode = tunables.IMPLICIT_TILED_GATHER_MODE if tunables.IMPLICIT_TILED_GATHER_MODE is defined else 0 %}
|
| 82 |
+
{% set implicitGatherWgSize = GEMM_WG_X * GEMM_WG_Y %}
|
| 83 |
+
{% set implicitGatherColumnVecs = (GEMM_BN / 4)|int %}
|
| 84 |
+
{% set implicitGatherKChunks = (implicitGatherWgSize / implicitGatherColumnVecs)|int %}
|
| 85 |
+
{% set implicitGatherDivides = implicitGatherWgSize % implicitGatherColumnVecs == 0 and implicitGatherKChunks > 0 and GEMM_BK % implicitGatherKChunks == 0 %}
|
| 86 |
+
{% set useCarriedImplicitGather = implicitIm2col and implicitGatherMode == 1 and implicitGatherDivides %}
|
| 87 |
+
{% if useCarriedImplicitGather %}
|
| 88 |
+
{% set implicitGatherLoadWidth = (GEMM_BK / implicitGatherKChunks)|int %}
|
| 89 |
+
{% endif %}
|
| 90 |
+
{% if implicitIm2col %}
|
| 91 |
+
const CONV_KERNEL_H: u32 = {{ convKernelH }}u;
|
| 92 |
+
const CONV_KERNEL_W: u32 = {{ convKernelW }}u;
|
| 93 |
+
const CONV_KSIZE: u32 = CONV_KERNEL_H * CONV_KERNEL_W;
|
| 94 |
+
const CONV_STRIDE_H: u32 = {{ convStrideH }}u;
|
| 95 |
+
const CONV_STRIDE_W: u32 = {{ convStrideW }}u;
|
| 96 |
+
const CONV_DILATION_H: u32 = {{ convDilationH }}u;
|
| 97 |
+
const CONV_DILATION_W: u32 = {{ convDilationW }}u;
|
| 98 |
+
const CONV_PAD_TOP: i32 = {{ convPadTop }};
|
| 99 |
+
const CONV_PAD_LEFT: i32 = {{ convPadLeft }};
|
| 100 |
+
const CONV_IN_H: u32 = {{ convInH }}u;
|
| 101 |
+
const CONV_IN_W: u32 = {{ convInW }}u;
|
| 102 |
+
const CONV_OUT_W: u32 = {{ convOutW }}u;
|
| 103 |
+
const CONV_IN_CHANNELS: u32 = {{ convInChannels }}u;
|
| 104 |
+
{% endif %}
|
| 105 |
+
|
| 106 |
+
{% if fusedNarrowProjection %}
|
| 107 |
+
|
| 108 |
+
const PROJECTION_OUT_C: u32 = {{ projectionChannels }}u;
|
| 109 |
+
{% else %}{% set emitConvStoreOut = not splitKPartial %}{% if emitConvStoreOut | default(true) %}fn store_out(m: u32, n: u32, yBase: u32, raw: f32) {
|
| 110 |
+
if (m >= params.M || n >= params.N) {
|
| 111 |
+
return;
|
| 112 |
+
}
|
| 113 |
+
var v = raw;
|
| 114 |
+
{% if hasBias %}
|
| 115 |
+
v = v + f32(bias[m]);
|
| 116 |
+
{% endif %}
|
| 117 |
+
{% if hasZ %}
|
| 118 |
+
v = v + f32(zResidual[yBase + m * params.N + n]);
|
| 119 |
+
{% endif %}
|
| 120 |
+
{% if hasActivation %}
|
| 121 |
+
v = fused_act(v);
|
| 122 |
+
{% endif %}
|
| 123 |
+
y[yBase + m * params.N + n] = {{ T }}(v);
|
| 124 |
+
}
|
| 125 |
+
{% endif %}
|
| 126 |
+
{% endif %}
|
| 127 |
+
|
| 128 |
+
var<workgroup> tileA: array<array<vec4<{{ tileT }}>, AK_VECS>, BM>;
|
| 129 |
+
var<workgroup> tileB: array<array<vec4<{{ tileT }}>, BN_VECS>, BK>;
|
| 130 |
+
{% if fusedNarrowProjection and GEMM_BK < GEMM_BM %}
|
| 131 |
+
// BK16 cannot reuse the 16-row input tile to publish a BM32 intermediate.
|
| 132 |
+
// The extra 32x64 f32 tile keeps the total at 14 KiB, below WebGPU's
|
| 133 |
+
// guaranteed 16 KiB workgroup-storage floor.
|
| 134 |
+
var<workgroup> projectionTile: array<array<vec4<f32>, BN_VECS>, BM>;
|
| 135 |
+
{% endif %}
|
| 136 |
+
{% macro publish_projection_input(localRow, localVec, component, globalRow, globalColumn, raw) %}
|
| 137 |
+
if ({{ globalRow }} < M && {{ globalColumn }} < N) {
|
| 138 |
+
var projectionInput = {{ raw }};
|
| 139 |
+
{% if hasBias %}
|
| 140 |
+
projectionInput = projectionInput + f32(bias[{{ globalRow }}]);
|
| 141 |
+
{% endif %}
|
| 142 |
+
{% if projectionInputAct == "relu" %}
|
| 143 |
+
projectionInput = projection_relu(projectionInput);
|
| 144 |
+
{% endif %}
|
| 145 |
+
{{ narrowProjectionTile }}[{{ localRow }}][{{ localVec }}].{{ component }} = projectionInput;
|
| 146 |
+
} else {
|
| 147 |
+
{{ narrowProjectionTile }}[{{ localRow }}][{{ localVec }}].{{ component }} = 0.0;
|
| 148 |
+
}
|
| 149 |
+
{%- endmacro %}
|
| 150 |
+
{% macro load_a_vec4(rowBase, columnBase, rowLimit, columnLimit) %}
|
| 151 |
+
for (var linear = li; linear < BM * AK_VECS; linear += WG_SIZE) {
|
| 152 |
+
let ar = linear / AK_VECS;
|
| 153 |
+
let ac4 = linear % AK_VECS;
|
| 154 |
+
let am = {{ rowBase }} + ar;
|
| 155 |
+
let ak = {{ columnBase }} + ac4 * 4u;
|
| 156 |
+
var av = vec4<{{ tileT }}>({{ tileT }}(0.0));
|
| 157 |
+
if (am < {{ rowLimit }}) {
|
| 158 |
+
let aBase = am * K + ak;
|
| 159 |
+
if (ak + 3u < {{ columnLimit }}) {
|
| 160 |
+
av = vec4<{{ tileT }}>({{ tileT }}(w[aBase]), {{ tileT }}(w[aBase + 1u]), {{ tileT }}(w[aBase + 2u]), {{ tileT }}(w[aBase + 3u]));
|
| 161 |
+
} else {
|
| 162 |
+
for (var t = 0u; t < 4u; t = t + 1u) {
|
| 163 |
+
if (ak + t < {{ columnLimit }}) {
|
| 164 |
+
av[t] = {{ tileT }}(w[aBase + t]);
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
tileA[ar][ac4] = av;
|
| 170 |
+
}
|
| 171 |
+
{%- endmacro %}
|
| 172 |
+
{% macro load_b_vec4(rowBase, columnBase, rowLimit, columnLimit) %}
|
| 173 |
+
for (var linear = li; linear < BK * BN_VECS; linear += WG_SIZE) {
|
| 174 |
+
let br = linear / BN_VECS;
|
| 175 |
+
let bc4 = linear % BN_VECS;
|
| 176 |
+
let bk = {{ rowBase }} + br;
|
| 177 |
+
let bn = {{ columnBase }} + bc4 * 4u;
|
| 178 |
+
var bvec = vec4<{{ tileT }}>({{ tileT }}(0.0));
|
| 179 |
+
if (bk < {{ rowLimit }}) {
|
| 180 |
+
let bBase = xBatchBase + bk * N + bn;
|
| 181 |
+
if (bn + 3u < {{ columnLimit }}) {
|
| 182 |
+
bvec = vec4<{{ tileT }}>({{ tileT }}(xm[bBase]), {{ tileT }}(xm[bBase + 1u]), {{ tileT }}(xm[bBase + 2u]), {{ tileT }}(xm[bBase + 3u]));
|
| 183 |
+
} else {
|
| 184 |
+
for (var t = 0u; t < 4u; t = t + 1u) {
|
| 185 |
+
if (bn + t < {{ columnLimit }}) {
|
| 186 |
+
bvec[t] = {{ tileT }}(xm[bBase + t]);
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
}
|
| 191 |
+
tileB[br][bc4] = bvec;
|
| 192 |
+
}
|
| 193 |
+
{%- endmacro %}
|
| 194 |
+
{% macro load_b_implicit_vec4(rowBase, columnBase, rowLimit, columnLimit) %}
|
| 195 |
+
for (var linear = li; linear < BK * BN_VECS; linear += WG_SIZE) {
|
| 196 |
+
let br = linear / BN_VECS;
|
| 197 |
+
let bc4 = linear % BN_VECS;
|
| 198 |
+
let bk = {{ rowBase }} + br;
|
| 199 |
+
var bvec = vec4<{{ tileT }}>({{ tileT }}(0.0));
|
| 200 |
+
if (bk < {{ rowLimit }}) {
|
| 201 |
+
// k is the OIHW weight flattening (ic * KH + kh) * KW + kw; n is the output position.
|
| 202 |
+
let ic = bk / CONV_KSIZE;
|
| 203 |
+
let kq = bk % CONV_KSIZE;
|
| 204 |
+
let kh = kq / CONV_KERNEL_W;
|
| 205 |
+
let kw = kq % CONV_KERNEL_W;
|
| 206 |
+
let planeBase = (batch * CONV_IN_CHANNELS + ic) * CONV_IN_H;
|
| 207 |
+
for (var t = 0u; t < 4u; t = t + 1u) {
|
| 208 |
+
let bn = {{ columnBase }} + bc4 * 4u + t;
|
| 209 |
+
if (bn < {{ columnLimit }}) {
|
| 210 |
+
let oh = bn / CONV_OUT_W;
|
| 211 |
+
let ow = bn % CONV_OUT_W;
|
| 212 |
+
let ih = i32(oh * CONV_STRIDE_H + kh * CONV_DILATION_H) - CONV_PAD_TOP;
|
| 213 |
+
let iw = i32(ow * CONV_STRIDE_W + kw * CONV_DILATION_W) - CONV_PAD_LEFT;
|
| 214 |
+
if (ih >= 0 && ih < i32(CONV_IN_H) && iw >= 0 && iw < i32(CONV_IN_W)) {
|
| 215 |
+
bvec[t] = {{ tileT }}(xm[(planeBase + u32(ih)) * CONV_IN_W + u32(iw)]);
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
}
|
| 219 |
+
}
|
| 220 |
+
tileB[br][bc4] = bvec;
|
| 221 |
+
}
|
| 222 |
+
{%- endmacro %}
|
| 223 |
+
{% macro load_b_implicit_carried(rowBase, columnBase, rowLimit, columnLimit) %}
|
| 224 |
+
let bColVec = li / {{ implicitGatherKChunks }}u;
|
| 225 |
+
let bChunk = li % {{ implicitGatherKChunks }}u;
|
| 226 |
+
let bLocalK = bChunk * {{ implicitGatherLoadWidth }}u;
|
| 227 |
+
let bStartK = {{ rowBase }} + bLocalK;
|
| 228 |
+
let bCol0 = {{ columnBase }} + bColVec * 4u;
|
| 229 |
+
let bCols = vec4<u32>(bCol0, bCol0 + 1u, bCol0 + 2u, bCol0 + 3u);
|
| 230 |
+
let inColumn = bCols < vec4<u32>({{ columnLimit }});
|
| 231 |
+
let ih0 = vec4<i32>((bCols / vec4<u32>(CONV_OUT_W)) * CONV_STRIDE_H) - vec4<i32>(CONV_PAD_TOP);
|
| 232 |
+
let iw0 = vec4<i32>((bCols % vec4<u32>(CONV_OUT_W)) * CONV_STRIDE_W) - vec4<i32>(CONV_PAD_LEFT);
|
| 233 |
+
var carriedK = bStartK;
|
| 234 |
+
var carriedIc = carriedK / CONV_KSIZE;
|
| 235 |
+
let carriedKq = carriedK % CONV_KSIZE;
|
| 236 |
+
var carriedKh = carriedKq / CONV_KERNEL_W;
|
| 237 |
+
var carriedKw = carriedKq % CONV_KERNEL_W;
|
| 238 |
+
var carriedIh = ih0 + vec4<i32>(i32(carriedKh * CONV_DILATION_H));
|
| 239 |
+
var carriedIw = iw0 + vec4<i32>(i32(carriedKw * CONV_DILATION_W));
|
| 240 |
+
|
| 241 |
+
// Interior words can carry four addresses. Padding, the N tail, and the
|
| 242 |
+
// final K tile retain the coordinate-checked loop below and publish exact
|
| 243 |
+
// zeros.
|
| 244 |
+
let interior = all(inColumn)
|
| 245 |
+
&& bStartK + {{ implicitGatherLoadWidth }}u <= {{ rowLimit }}
|
| 246 |
+
&& all(ih0 >= vec4<i32>(0))
|
| 247 |
+
&& all(ih0 + vec4<i32>(i32((CONV_KERNEL_H - 1u) * CONV_DILATION_H)) < vec4<i32>(i32(CONV_IN_H)))
|
| 248 |
+
&& all(iw0 >= vec4<i32>(0))
|
| 249 |
+
&& all(iw0 + vec4<i32>(i32((CONV_KERNEL_W - 1u) * CONV_DILATION_W)) < vec4<i32>(i32(CONV_IN_W)));
|
| 250 |
+
if (interior) {
|
| 251 |
+
let batchBase = batch * CONV_IN_CHANNELS * CONV_IN_H * CONV_IN_W;
|
| 252 |
+
let colStep = i32(CONV_DILATION_W);
|
| 253 |
+
let rowStep = i32(CONV_DILATION_H * CONV_IN_W);
|
| 254 |
+
let planeStep = i32(CONV_IN_H * CONV_IN_W);
|
| 255 |
+
let kwWrap = colStep * i32(CONV_KERNEL_W) - rowStep;
|
| 256 |
+
let khWrap = rowStep * i32(CONV_KERNEL_H) - planeStep;
|
| 257 |
+
var carriedAddress = vec4<i32>(i32(carriedIc) * planeStep) + carriedIh * vec4<i32>(i32(CONV_IN_W)) + carriedIw;
|
| 258 |
+
for (var i = 0u; i < {{ implicitGatherLoadWidth }}u; i = i + 1u) {
|
| 259 |
+
tileB[bLocalK + i][bColVec] = vec4<{{ tileT }}>(
|
| 260 |
+
{% for component in components %}
|
| 261 |
+
{{ tileT }}(xm[batchBase + u32(carriedAddress.{{ component }})]){% if not loop.last %},{% endif %}
|
| 262 |
+
{% endfor %}
|
| 263 |
+
);
|
| 264 |
+
carriedAddress = carriedAddress + vec4<i32>(colStep);
|
| 265 |
+
carriedKw = carriedKw + 1u;
|
| 266 |
+
if (carriedKw == CONV_KERNEL_W) {
|
| 267 |
+
carriedKw = 0u;
|
| 268 |
+
carriedAddress = carriedAddress - vec4<i32>(kwWrap);
|
| 269 |
+
carriedKh = carriedKh + 1u;
|
| 270 |
+
if (carriedKh == CONV_KERNEL_H) {
|
| 271 |
+
carriedKh = 0u;
|
| 272 |
+
carriedAddress = carriedAddress - vec4<i32>(khWrap);
|
| 273 |
+
}
|
| 274 |
+
}
|
| 275 |
+
}
|
| 276 |
+
} else {
|
| 277 |
+
for (var i = 0u; i < {{ implicitGatherLoadWidth }}u; i = i + 1u) {
|
| 278 |
+
var carriedValue = vec4<{{ tileT }}>({{ tileT }}(0.0));
|
| 279 |
+
let carriedRow = (batch * CONV_IN_CHANNELS + carriedIc) * CONV_IN_H;
|
| 280 |
+
let carriedOk = inColumn
|
| 281 |
+
& (carriedIh >= vec4<i32>(0)) & (carriedIh < vec4<i32>(i32(CONV_IN_H)))
|
| 282 |
+
& (carriedIw >= vec4<i32>(0)) & (carriedIw < vec4<i32>(i32(CONV_IN_W)));
|
| 283 |
+
if (carriedK < {{ rowLimit }}) {
|
| 284 |
+
{% for component in components %}
|
| 285 |
+
if (carriedOk.{{ component }}) {
|
| 286 |
+
carriedValue.{{ component }} = {{ tileT }}(xm[(carriedRow + u32(carriedIh.{{ component }})) * CONV_IN_W + u32(carriedIw.{{ component }})]);
|
| 287 |
+
}
|
| 288 |
+
{% endfor %}
|
| 289 |
+
}
|
| 290 |
+
tileB[bLocalK + i][bColVec] = carriedValue;
|
| 291 |
+
carriedK = carriedK + 1u;
|
| 292 |
+
carriedKw = carriedKw + 1u;
|
| 293 |
+
carriedIw = carriedIw + vec4<i32>(i32(CONV_DILATION_W));
|
| 294 |
+
if (carriedKw == CONV_KERNEL_W) {
|
| 295 |
+
carriedKw = 0u;
|
| 296 |
+
carriedIw = iw0;
|
| 297 |
+
carriedKh = carriedKh + 1u;
|
| 298 |
+
carriedIh = carriedIh + vec4<i32>(i32(CONV_DILATION_H));
|
| 299 |
+
if (carriedKh == CONV_KERNEL_H) {
|
| 300 |
+
carriedKh = 0u;
|
| 301 |
+
carriedIh = ih0;
|
| 302 |
+
carriedIc = carriedIc + 1u;
|
| 303 |
+
}
|
| 304 |
+
}
|
| 305 |
+
}
|
| 306 |
+
}
|
| 307 |
+
{%- endmacro %}
|
| 308 |
+
|
| 309 |
+
@compute @workgroup_size({{ GEMM_WG_X }}, {{ GEMM_WG_Y }}, 1)
|
| 310 |
+
fn main(
|
| 311 |
+
@builtin(workgroup_id) wg: vec3<u32>,
|
| 312 |
+
@builtin(local_invocation_id) lid: vec3<u32>
|
| 313 |
+
) {
|
| 314 |
+
let M = params.M;
|
| 315 |
+
let K = params.K;
|
| 316 |
+
let N = params.N;
|
| 317 |
+
let mBase = wg.y * BM;
|
| 318 |
+
let nBase = wg.x * BN;
|
| 319 |
+
let batch = wg.z;
|
| 320 |
+
{% if not implicitIm2col %}
|
| 321 |
+
let xBatchBase = batch * K * N;
|
| 322 |
+
{% endif %}
|
| 323 |
+
let li = lid.y * WG_X + lid.x;
|
| 324 |
+
|
| 325 |
+
// Explicit vectors give the backend fixed register indices and expose TM
|
| 326 |
+
// independent FMA chains.
|
| 327 |
+
{% for row in range(GEMM_TM) %}
|
| 328 |
+
var acc{{ row }} = vec{{ GEMM_TN }}<f32>(0.0);
|
| 329 |
+
{% endfor %}
|
| 330 |
+
|
| 331 |
+
let numTiles = (K + BK - 1u) / BK;
|
| 332 |
+
for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
|
| 333 |
+
let kBase = kt * BK;
|
| 334 |
+
{{ load_a_vec4("mBase", "kBase", "M", gemmKLimit) }}
|
| 335 |
+
{% if useCarriedImplicitGather %}
|
| 336 |
+
{{ load_b_implicit_carried("kBase", "nBase", gemmKLimit, "N") }}
|
| 337 |
+
{% elif implicitIm2col %}
|
| 338 |
+
{{ load_b_implicit_vec4("kBase", "nBase", gemmKLimit, "N") }}
|
| 339 |
+
{% else %}
|
| 340 |
+
{{ load_b_vec4("kBase", "nBase", gemmKLimit, "N") }}
|
| 341 |
+
{% endif %}
|
| 342 |
+
workgroupBarrier();
|
| 343 |
+
// One iteration consumes four K steps: TM vector words of A and four of B
|
| 344 |
+
// cover TM x TN x 4 FMAs.
|
| 345 |
+
let aRow = lid.y * TM;
|
| 346 |
+
for (var kv: u32 = 0u; kv < AK_VECS; kv = kv + 1u) {
|
| 347 |
+
{% for row in range(GEMM_TM) %}
|
| 348 |
+
let av{{ row }} = vec4<f32>(tileA[aRow + {{ row }}u][kv]);
|
| 349 |
+
{% endfor %}
|
| 350 |
+
{% for sub in range(4) %}
|
| 351 |
+
let bv{{ sub }} = vec{{ GEMM_TN }}<f32>(tileB[kv * 4u + {{ sub }}u][lid.x]);
|
| 352 |
+
{% for row in range(GEMM_TM) %}
|
| 353 |
+
acc{{ row }} += vec{{ GEMM_TN }}<f32>(av{{ row }}.{{ components[sub] }}) * bv{{ sub }};
|
| 354 |
+
{% endfor %}
|
| 355 |
+
{% endfor %}
|
| 356 |
+
}
|
| 357 |
+
workgroupBarrier();
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
{% if not fusedNarrowProjection %}
|
| 361 |
+
let yBatchBase = batch * M * N;
|
| 362 |
+
{% endif %}
|
| 363 |
+
let m0 = mBase + lid.y * TM;
|
| 364 |
+
let n0 = nBase + lid.x * TN;
|
| 365 |
+
{% for row in range(GEMM_TM) %}
|
| 366 |
+
{% for column in range(GEMM_TN) %}
|
| 367 |
+
{% if fusedNarrowProjection %}
|
| 368 |
+
{{ 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]) }}
|
| 369 |
+
{% else %}
|
| 370 |
+
store_out(m0 + {{ row }}u, n0 + {{ column }}u, yBatchBase, acc{{ row }}.{{ components[column] }});
|
| 371 |
+
{% endif %}
|
| 372 |
+
{% endfor %}
|
| 373 |
+
{% endfor %}{% if fusedNarrowProjection %}
|
| 374 |
+
|
| 375 |
+
// Conv's final K-tile barrier makes tileB dead before it becomes the
|
| 376 |
+
// intermediate tile. Every lane publishes its unique micro-tile, then one
|
| 377 |
+
// lane per spatial vector word consumes all logical M rows in increasing
|
| 378 |
+
// order, four adjacent columns at a time. Invalid M/N-tail cells are
|
| 379 |
+
// initialized above, so the barrier is uniform and no lane can observe a
|
| 380 |
+
// stale input-tile value.
|
| 381 |
+
workgroupBarrier();
|
| 382 |
+
if (li < BN_VECS) {
|
| 383 |
+
let projectionNBase = nBase + li * 4u;
|
| 384 |
+
{% for oc in range(projectionChannels) %}
|
| 385 |
+
var projectionAcc{{ oc }} = vec4<f32>(0.0);
|
| 386 |
+
{% endfor %}
|
| 387 |
+
for (var channel = 0u; channel < M; channel = channel + 1u) {
|
| 388 |
+
let projectionValues = vec4<f32>({{ narrowProjectionTile }}[channel][li]);
|
| 389 |
+
{% for oc in range(projectionChannels) %}
|
| 390 |
+
projectionAcc{{ oc }} = projectionAcc{{ oc }} + projectionValues * f32(projectionW[{{ oc }}u * M + channel]);
|
| 391 |
+
{% endfor %}
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
let projectionYBase = batch * PROJECTION_OUT_C * N;
|
| 395 |
+
{% for oc in range(projectionChannels) %}
|
| 396 |
+
let projected{{ oc }} = projectionAcc{{ oc }}{% if projectionBiased %} + vec4<f32>(f32(projectionBias[{{ oc }}u])){% endif %};
|
| 397 |
+
{% endfor %}
|
| 398 |
+
{% for column in range(4) %}
|
| 399 |
+
let projectionN{{ column }} = projectionNBase + {{ column }}u;
|
| 400 |
+
if (projectionN{{ column }} < N) {
|
| 401 |
+
{% for oc in range(projectionChannels) %}
|
| 402 |
+
{% if projectionOutputAct == "relu" %}
|
| 403 |
+
let activated{{ oc }}_{{ column }} = projection_relu(projected{{ oc }}.{{ components[column] }});
|
| 404 |
+
{% elif projectionOutputAct == "sigmoid" %}
|
| 405 |
+
let activated{{ oc }}_{{ column }} = sigmoid_safe(projected{{ oc }}.{{ components[column] }});
|
| 406 |
+
{% else %}
|
| 407 |
+
let activated{{ oc }}_{{ column }} = projected{{ oc }}.{{ components[column] }};
|
| 408 |
+
{% endif %}
|
| 409 |
+
{% if projectionScaled %}
|
| 410 |
+
y[projectionYBase + {{ oc }}u * N + projectionN{{ column }}] = activated{{ oc }}_{{ column }} * params.outputScale;
|
| 411 |
+
{% else %}
|
| 412 |
+
y[projectionYBase + {{ oc }}u * N + projectionN{{ column }}] = activated{{ oc }}_{{ column }};
|
| 413 |
+
{% endif %}
|
| 414 |
+
{% endfor %}
|
| 415 |
+
}
|
| 416 |
+
{% endfor %}
|
| 417 |
+
}
|
| 418 |
+
{% endif %}
|
| 419 |
+
}
|
build/webgpu/conv-1x1-gemm-tiled.wgsl.jinja
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// 1x1 Conv routed as a batched GEMM: Y[b] (M x N) = W (M x K) * X[b] (K x N)
|
| 2 |
+
// with M = outChannels, K = inChannels, N = H*W. For a 1x1 kernel with
|
| 3 |
+
// group == 1, stride 1, no padding, and dilation 1, the NCHW input is already
|
| 4 |
+
// the [K, N] matrix and the OIHW weight is already the [M, K] matrix — no
|
| 5 |
+
// data movement, only index arithmetic.
|
| 6 |
+
//
|
| 7 |
+
// Register-blocked 32x32 shared-memory tile (16x16 threads, each computes a 2x2
|
| 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 |
+
fn fused_act(v: f32) -> f32 {
|
| 26 |
+
{% if activation == "Relu" %}
|
| 27 |
+
return max(v, 0.0);
|
| 28 |
+
{% elif activation == "Clip" %}
|
| 29 |
+
return clamp(v, f32({{ actAlpha }}), f32({{ actBeta }}));
|
| 30 |
+
{% elif activation == "LeakyRelu" %}
|
| 31 |
+
return select(v * f32({{ actAlpha }}), v, v >= 0.0);
|
| 32 |
+
{% elif activation == "Sigmoid" %}
|
| 33 |
+
return 1.0 / (1.0 + exp(-v));
|
| 34 |
+
{% elif activation == "Tanh" %}
|
| 35 |
+
return tanh(v);
|
| 36 |
+
{% elif activation == "HardSigmoid" %}
|
| 37 |
+
return clamp(f32({{ actAlpha }}) * v + f32({{ actBeta }}), 0.0, 1.0);
|
| 38 |
+
{% else %}
|
| 39 |
+
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 40 |
+
{% endif %}
|
| 41 |
+
}
|
| 42 |
+
{% endif %}
|
| 43 |
+
|
| 44 |
+
{% set tileT = "f16" if usesF16 else "f32" -%}
|
| 45 |
+
const BK: u32 = 16u;
|
| 46 |
+
const BM: u32 = 32u;
|
| 47 |
+
const BN: u32 = 32u;
|
| 48 |
+
|
| 49 |
+
// Store one output element with the optional bias/residual/activation epilogue in the
|
| 50 |
+
// f32 accumulator domain: Y = activation(conv + bias + Z).
|
| 51 |
+
fn store_out(m: u32, n: u32, yBase: u32, raw: f32) {
|
| 52 |
+
if (m >= params.M || n >= params.N) {
|
| 53 |
+
return;
|
| 54 |
+
}
|
| 55 |
+
var v = raw;
|
| 56 |
+
{% if hasBias %}
|
| 57 |
+
v = v + f32(bias[m]);
|
| 58 |
+
{% endif %}
|
| 59 |
+
{% if hasZ %}
|
| 60 |
+
v = v + f32(zResidual[yBase + m * params.N + n]);
|
| 61 |
+
{% endif %}
|
| 62 |
+
{% if hasActivation %}
|
| 63 |
+
v = fused_act(v);
|
| 64 |
+
{% endif %}
|
| 65 |
+
y[yBase + m * params.N + n] = {{ T }}(v);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
var<workgroup> tileA: array<array<{{ tileT }}, BK>, BM>;
|
| 70 |
+
var<workgroup> tileB: array<array<{{ tileT }}, BN>, BK>;
|
| 71 |
+
|
| 72 |
+
@compute @workgroup_size(16, 16, 1)
|
| 73 |
+
fn main(
|
| 74 |
+
@builtin(workgroup_id) wg: vec3<u32>,
|
| 75 |
+
@builtin(local_invocation_id) lid: vec3<u32>
|
| 76 |
+
) {
|
| 77 |
+
let M = params.M;
|
| 78 |
+
let K = params.K;
|
| 79 |
+
let N = params.N;
|
| 80 |
+
let mBase = wg.y * BM;
|
| 81 |
+
let nBase = wg.x * BN;
|
| 82 |
+
let batch = wg.z;
|
| 83 |
+
let xBatchBase = batch * K * N;
|
| 84 |
+
let li = lid.y * 16u + lid.x;
|
| 85 |
+
|
| 86 |
+
var acc00: f32 = 0.0;
|
| 87 |
+
var acc01: f32 = 0.0;
|
| 88 |
+
var acc10: f32 = 0.0;
|
| 89 |
+
var acc11: f32 = 0.0;
|
| 90 |
+
let numTiles = (K + BK - 1u) / BK;
|
| 91 |
+
for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
|
| 92 |
+
let kBase = kt * BK;
|
| 93 |
+
// Cooperative load: 32x16 A(=W) tile + 16x32 B(=X[batch]) tile, 256 threads x 2 each.
|
| 94 |
+
for (var e: u32 = 0u; e < (BM * BK) / 256u; e = e + 1u) {
|
| 95 |
+
let idx = li + e * 256u;
|
| 96 |
+
let ar = idx / BK;
|
| 97 |
+
let ac = idx % BK;
|
| 98 |
+
let am = mBase + ar;
|
| 99 |
+
let ak = kBase + ac;
|
| 100 |
+
if (am < M && ak < K) {
|
| 101 |
+
tileA[ar][ac] = {{ tileT }}(w[am * K + ak]);
|
| 102 |
+
} else {
|
| 103 |
+
tileA[ar][ac] = {{ tileT }}(0.0);
|
| 104 |
+
}
|
| 105 |
+
let br = idx / BN;
|
| 106 |
+
let bc = idx % BN;
|
| 107 |
+
let bk = kBase + br;
|
| 108 |
+
let bn = nBase + bc;
|
| 109 |
+
if (bk < K && bn < N) {
|
| 110 |
+
tileB[br][bc] = {{ tileT }}(xm[xBatchBase + bk * N + bn]);
|
| 111 |
+
} else {
|
| 112 |
+
tileB[br][bc] = {{ tileT }}(0.0);
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
workgroupBarrier();
|
| 116 |
+
for (var kk: u32 = 0u; kk < BK; kk = kk + 1u) {
|
| 117 |
+
let a0 = f32(tileA[lid.y * 2u][kk]);
|
| 118 |
+
let a1 = f32(tileA[lid.y * 2u + 1u][kk]);
|
| 119 |
+
let b0 = f32(tileB[kk][lid.x * 2u]);
|
| 120 |
+
let b1 = f32(tileB[kk][lid.x * 2u + 1u]);
|
| 121 |
+
acc00 = acc00 + a0 * b0;
|
| 122 |
+
acc01 = acc01 + a0 * b1;
|
| 123 |
+
acc10 = acc10 + a1 * b0;
|
| 124 |
+
acc11 = acc11 + a1 * b1;
|
| 125 |
+
}
|
| 126 |
+
workgroupBarrier();
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
let m0 = mBase + lid.y * 2u;
|
| 130 |
+
let m1 = m0 + 1u;
|
| 131 |
+
let n0 = nBase + lid.x * 2u;
|
| 132 |
+
let n1 = n0 + 1u;
|
| 133 |
+
let yBatchBase = batch * M * N;
|
| 134 |
+
store_out(m0, n0, yBatchBase, acc00);
|
| 135 |
+
store_out(m0, n1, yBatchBase, acc01);
|
| 136 |
+
store_out(m1, n0, yBatchBase, acc10);
|
| 137 |
+
store_out(m1, n1, yBatchBase, acc11);
|
| 138 |
+
}
|
build/webgpu/conv-1x1-subgroup-matrix.wgsl.jinja
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Convolution as subgroup-matrix GEMM. OIHW weights form row-major [M,K]; `xm`
|
| 2 |
+
// is either an NCHW 1x1 view [K,N] or a materialized im2col matrix. Bias is
|
| 3 |
+
// applied per output channel after the full f32 accumulation.
|
| 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
|
| 7 |
+
// zero-fills directly from NCHW without materializing the column matrix.
|
| 8 |
+
//
|
| 9 |
+
// `splitK` assigns disjoint K ranges to workgroup_id.z. Each slice writes raw
|
| 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 |
+
{% if usesF16 %}
|
| 20 |
+
enable f16;
|
| 21 |
+
{% endif %}
|
| 22 |
+
enable subgroups;
|
| 23 |
+
{% if pinSubgroupSize32 %}
|
| 24 |
+
enable subgroup_size_control;
|
| 25 |
+
{% endif %}
|
| 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" %}
|
| 33 |
+
{% set useDirectMatrixStore = directMatrixStore is defined and directMatrixStore %}
|
| 34 |
+
{% set tileRowsValue = tileRows if tileRows is defined else 32 %}
|
| 35 |
+
{% set tileColsValue = tileCols if tileCols is defined else 64 %}
|
| 36 |
+
{% set workgroupThreadsValue = workgroupThreads if workgroupThreads is defined else 128 %}
|
| 37 |
+
{% set subgroupRowsValue = subgroupRows if subgroupRows is defined else 2 %}
|
| 38 |
+
{% set subgroupColsValue = subgroupCols if subgroupCols is defined else 2 %}
|
| 39 |
+
{% set subRowsValue = (tileRowsValue / subgroupRowsValue)|int %}
|
| 40 |
+
{% set subColsValue = (tileColsValue / subgroupColsValue)|int %}
|
| 41 |
+
{% set bLoadWidth = (tileColsValue * 32 / workgroupThreadsValue)|int %}
|
| 42 |
+
{% set bKChunks = (32 / bLoadWidth)|int %}
|
| 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;
|
| 72 |
+
for (var col_offset = 0u; col_offset < 8u; col_offset = col_offset + 1u) {
|
| 73 |
+
let k = k_idx + col + col_offset;
|
| 74 |
+
if (a_global < M{% if padded %} && k < K{% endif %}) {
|
| 75 |
+
{% set A_PHASE = "phase_base + " if polyphase else "" %}
|
| 76 |
+
{% if operandScalar == "f16" %}
|
| 77 |
+
tile_A[row * TILE_K + col + col_offset] = f16(w[{{ A_PHASE }}a_global * K + k]);
|
| 78 |
+
{% else %}
|
| 79 |
+
tile_A[row * TILE_K + col + col_offset] = f32(w[{{ A_PHASE }}a_global * K + k]);
|
| 80 |
+
{% endif %}
|
| 81 |
+
} else {
|
| 82 |
+
{% if operandScalar == "f16" %}
|
| 83 |
+
tile_A[row * TILE_K + col + col_offset] = 0.0h;
|
| 84 |
+
{% else %}
|
| 85 |
+
tile_A[row * TILE_K + col + col_offset] = 0.0;
|
| 86 |
+
{% endif %}
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
|
| 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" %}
|
| 97 |
+
tile_B[row * TILE_K + col + i] = f16(xm[b_base + k * {{ nColsVar }} + b_col]);
|
| 98 |
+
{% else %}
|
| 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 |
+
fn fused_act(v: f32) -> f32 {
|
| 109 |
+
{% if activation == "Relu" %}
|
| 110 |
+
return max(v, 0.0);
|
| 111 |
+
{% elif activation == "Clip" %}
|
| 112 |
+
return clamp(v, f32({{ actAlpha }}), f32({{ actBeta }}));
|
| 113 |
+
{% elif activation == "LeakyRelu" %}
|
| 114 |
+
return select(v * f32({{ actAlpha }}), v, v >= 0.0);
|
| 115 |
+
{% elif activation == "Sigmoid" %}
|
| 116 |
+
return 1.0 / (1.0 + exp(-v));
|
| 117 |
+
{% elif activation == "Tanh" %}
|
| 118 |
+
return tanh(v);
|
| 119 |
+
{% elif activation == "HardSigmoid" %}
|
| 120 |
+
return clamp(f32({{ actAlpha }}) * v + f32({{ actBeta }}), 0.0, 1.0);
|
| 121 |
+
{% else %}
|
| 122 |
+
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 123 |
+
{% endif %}
|
| 124 |
+
}
|
| 125 |
+
{% endif %}
|
| 126 |
+
|
| 127 |
+
{% set biasAdd = " + bv" if hasBias else "" %}
|
| 128 |
+
{% if hasActivation or hasZ %}
|
| 129 |
+
// Fused epilogue: Y = activation(conv + bias + Z), applied at the output store.
|
| 130 |
+
// `raw` is the f32 convolution-plus-bias accumulator; Z shares Y's NCHW layout.
|
| 131 |
+
fn epi(raw: f32{% if hasZ %}, yIndex: u32{% endif %}) -> {{ T }} {
|
| 132 |
+
var r = raw;
|
| 133 |
+
{% if hasZ %}
|
| 134 |
+
r = r + f32(zResidual[yIndex]);
|
| 135 |
+
{% endif %}
|
| 136 |
+
{% if hasActivation %}
|
| 137 |
+
r = fused_act(r);
|
| 138 |
+
{% endif %}
|
| 139 |
+
return {{ T }}(r);
|
| 140 |
+
}
|
| 141 |
+
{% endif %}
|
| 142 |
+
{% macro store_val(valExpr, idxExpr) %}
|
| 143 |
+
{% if hasActivation or hasZ %}epi({{ valExpr }}{% if hasZ %}, {{ idxExpr }}{% endif %})
|
| 144 |
+
{%- else %}{{ T }}({{ valExpr }})
|
| 145 |
+
{%- endif %}
|
| 146 |
+
{% endmacro %}
|
| 147 |
+
|
| 148 |
+
{% if (not useDirectMatrixStore or padded) and not splitKPartial and not fusedNarrowProjection %}
|
| 149 |
+
fn storeOutput(offset: u32, {% if hasBias or polyphase %}row_base: u32, {% endif %}row: u32, col: u32, src_slot: u32, row_limit: i32{% if padded %}, col_anchor: u32{% endif %}) {
|
| 150 |
+
if (row_limit > 0 && row < u32(row_limit)) {
|
| 151 |
+
let col2 = col + 1u;
|
| 152 |
+
{% if hasBias %}
|
| 153 |
+
let bv = {{ accScalar }}(bias[row_base + row]);
|
| 154 |
+
{% endif %}
|
| 155 |
+
{% if padded %}
|
| 156 |
+
// Padded GEMM: the last column tile runs past the logical N; only write
|
| 157 |
+
// columns inside the real output. Each subgroup-matrix column maps to a
|
| 158 |
+
// distinct y column (anchor + local), so the guards are independent.
|
| 159 |
+
if (col_anchor + col + 0u < N) { y[offset + row * N + col] = {{ store_val("scratch[src_slot][0][row * 8u + col]" ~ biasAdd, "offset + row * N + col") }}; }
|
| 160 |
+
if (col_anchor + col + 8u < N) { y[offset + row * N + col + 8u] = {{ store_val("scratch[src_slot][1][row * 8u + col]" ~ biasAdd, "offset + row * N + col + 8u") }}; }
|
| 161 |
+
if (col_anchor + col + 16u < N) { y[offset + row * N + col + 16u] = {{ store_val("scratch[src_slot][2][row * 8u + col]" ~ biasAdd, "offset + row * N + col + 16u") }}; }
|
| 162 |
+
if (col_anchor + col + 24u < N) { y[offset + row * N + col + 24u] = {{ store_val("scratch[src_slot][3][row * 8u + col]" ~ biasAdd, "offset + row * N + col + 24u") }}; }
|
| 163 |
+
if (col_anchor + col2 + 0u < N) { y[offset + row * N + col2] = {{ store_val("scratch[src_slot][0][row * 8u + col2]" ~ biasAdd, "offset + row * N + col2") }}; }
|
| 164 |
+
if (col_anchor + col2 + 8u < N) { y[offset + row * N + col2 + 8u] = {{ store_val("scratch[src_slot][1][row * 8u + col2]" ~ biasAdd, "offset + row * N + col2 + 8u") }}; }
|
| 165 |
+
if (col_anchor + col2 + 16u < N) { y[offset + row * N + col2 + 16u] = {{ store_val("scratch[src_slot][2][row * 8u + col2]" ~ biasAdd, "offset + row * N + col2 + 16u") }}; }
|
| 166 |
+
if (col_anchor + col2 + 24u < N) { y[offset + row * N + col2 + 24u] = {{ store_val("scratch[src_slot][3][row * 8u + col2]" ~ biasAdd, "offset + row * N + col2 + 24u") }}; }
|
| 167 |
+
{% else %}
|
| 168 |
+
y[offset + row * N + col] = {{ store_val("scratch[src_slot][0][row * 8u + col]" ~ biasAdd, "offset + row * N + col") }};
|
| 169 |
+
y[offset + row * N + col + 8u] = {{ store_val("scratch[src_slot][1][row * 8u + col]" ~ biasAdd, "offset + row * N + col + 8u") }};
|
| 170 |
+
y[offset + row * N + col + 16u] = {{ store_val("scratch[src_slot][2][row * 8u + col]" ~ biasAdd, "offset + row * N + col + 16u") }};
|
| 171 |
+
y[offset + row * N + col + 24u] = {{ store_val("scratch[src_slot][3][row * 8u + col]" ~ biasAdd, "offset + row * N + col + 24u") }};
|
| 172 |
+
|
| 173 |
+
y[offset + row * N + col2] = {{ store_val("scratch[src_slot][0][row * 8u + col2]" ~ biasAdd, "offset + row * N + col2") }};
|
| 174 |
+
y[offset + row * N + col2 + 8u] = {{ store_val("scratch[src_slot][1][row * 8u + col2]" ~ biasAdd, "offset + row * N + col2 + 8u") }};
|
| 175 |
+
y[offset + row * N + col2 + 16u] = {{ store_val("scratch[src_slot][2][row * 8u + col2]" ~ biasAdd, "offset + row * N + col2 + 16u") }};
|
| 176 |
+
y[offset + row * N + col2 + 24u] = {{ store_val("scratch[src_slot][3][row * 8u + col2]" ~ biasAdd, "offset + row * N + col2 + 24u") }};
|
| 177 |
+
{% endif %}
|
| 178 |
+
}
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
{% endif %}
|
| 182 |
+
@compute @workgroup_size({{ workgroupThreadsValue }}, 1, 1){{ " @subgroup_size(32)" if pinSubgroupSize32 else "" }}
|
| 183 |
+
fn main(
|
| 184 |
+
@builtin(workgroup_id) workgroup_id: vec3<u32>,
|
| 185 |
+
@builtin(local_invocation_index) local_idx: u32,
|
| 186 |
+
{% if (not useDirectMatrixStore or padded) and not splitKPartial and not fusedNarrowProjection %}
|
| 187 |
+
@builtin(subgroup_invocation_id) sg_id: u32,
|
| 188 |
+
{% endif %}
|
| 189 |
+
@builtin(subgroup_size) sg_size: u32
|
| 190 |
+
) {
|
| 191 |
+
let batch = workgroup_id.z;
|
| 192 |
+
let b_base = batch * B_BATCH_STRIDE;
|
| 193 |
+
{% if not fusedNarrowProjection %}
|
| 194 |
+
let c_base = batch * C_BATCH_STRIDE;
|
| 195 |
+
{% endif %}
|
| 196 |
+
let a_global_base = workgroup_id.y * TILE_ROWS;
|
| 197 |
+
let b_global_base = workgroup_id.x * TILE_COLS;
|
| 198 |
+
let subtile_id = local_idx / sg_size;
|
| 199 |
+
let subtile_idx = subtile_id / {{ subgroupRowsValue }}u;
|
| 200 |
+
let subtile_idy = subtile_id % {{ subgroupRowsValue }}u;
|
| 201 |
+
let base_A = subtile_idy * SUB_ROWS;
|
| 202 |
+
let base_B = subtile_idx * SUB_COLS;
|
| 203 |
+
|
| 204 |
+
var matC00: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
|
| 205 |
+
var matC01: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
|
| 206 |
+
var matC02: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
|
| 207 |
+
var matC03: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
|
| 208 |
+
var matC10: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
|
| 209 |
+
var matC11: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
|
| 210 |
+
var matC12: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
|
| 211 |
+
var matC13: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
|
| 212 |
+
|
| 213 |
+
for (var kidx = 0u; kidx < {{ kLoopVar }}; kidx = kidx + TILE_K) {
|
| 214 |
+
loadSHMA(a_global_base, kidx, local_idx / 4u, local_idx % 4u);
|
| 215 |
+
loadSHMB(b_base, b_global_base, kidx, local_idx / {{ bKChunks }}u, local_idx % {{ bKChunks }}u);
|
| 216 |
+
workgroupBarrier();
|
| 217 |
+
|
| 218 |
+
for (var step = 0u; step < TILE_K; step = step + 8u) {
|
| 219 |
+
let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
|
| 220 |
+
var matA0: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset, false, TILE_K);
|
| 221 |
+
var matA1: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset + 8u * TILE_K, false, TILE_K);
|
| 222 |
+
|
| 223 |
+
let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
|
| 224 |
+
var matB0: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset, true, TILE_K);
|
| 225 |
+
var matB1: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 8u * TILE_K, true, TILE_K);
|
| 226 |
+
var matB2: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 16u * TILE_K, true, TILE_K);
|
| 227 |
+
var matB3: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 24u * TILE_K, true, TILE_K);
|
| 228 |
+
|
| 229 |
+
matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
|
| 230 |
+
matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
|
| 231 |
+
matC02 = subgroupMatrixMultiplyAccumulate(matA0, matB2, matC02);
|
| 232 |
+
matC03 = subgroupMatrixMultiplyAccumulate(matA0, matB3, matC03);
|
| 233 |
+
matC10 = subgroupMatrixMultiplyAccumulate(matA1, matB0, matC10);
|
| 234 |
+
matC11 = subgroupMatrixMultiplyAccumulate(matA1, matB1, matC11);
|
| 235 |
+
matC12 = subgroupMatrixMultiplyAccumulate(matA1, matB2, matC12);
|
| 236 |
+
matC13 = subgroupMatrixMultiplyAccumulate(matA1, matB3, matC13);
|
| 237 |
+
}
|
| 238 |
+
workgroupBarrier();
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
{% if fusedNarrowProjection %}
|
| 242 |
+
// The Conv producer owns exactly one 32-row tile. Its input tile is dead after
|
| 243 |
+
// the final K iteration, so reuse those 32x64 f32 cells as the only
|
| 244 |
+
// cross-subgroup handoff into the narrow projection. All collective stores
|
| 245 |
+
// remain subgroup-uniform, and every fragment has a disjoint destination.
|
| 246 |
+
let fused_tile_offset = base_A * TILE_COLS + base_B;
|
| 247 |
+
subgroupMatrixStore(&tile_B, fused_tile_offset + 0u * TILE_COLS + 0u, matC00, false, TILE_COLS);
|
| 248 |
+
subgroupMatrixStore(&tile_B, fused_tile_offset + 0u * TILE_COLS + 8u, matC01, false, TILE_COLS);
|
| 249 |
+
subgroupMatrixStore(&tile_B, fused_tile_offset + 0u * TILE_COLS + 16u, matC02, false, TILE_COLS);
|
| 250 |
+
subgroupMatrixStore(&tile_B, fused_tile_offset + 0u * TILE_COLS + 24u, matC03, false, TILE_COLS);
|
| 251 |
+
subgroupMatrixStore(&tile_B, fused_tile_offset + 8u * TILE_COLS + 0u, matC10, false, TILE_COLS);
|
| 252 |
+
subgroupMatrixStore(&tile_B, fused_tile_offset + 8u * TILE_COLS + 8u, matC11, false, TILE_COLS);
|
| 253 |
+
subgroupMatrixStore(&tile_B, fused_tile_offset + 8u * TILE_COLS + 16u, matC12, false, TILE_COLS);
|
| 254 |
+
subgroupMatrixStore(&tile_B, fused_tile_offset + 8u * TILE_COLS + 24u, matC13, false, TILE_COLS);
|
| 255 |
+
workgroupBarrier();
|
| 256 |
+
|
| 257 |
+
// One invocation owns one spatial column and walks the producer channels in
|
| 258 |
+
// increasing order to preserve the scalar projection's accumulation order.
|
| 259 |
+
// The inactive half of the workgroup has no remaining collective operation
|
| 260 |
+
// to reach.
|
| 261 |
+
let fused_global_col = b_global_base + local_idx;
|
| 262 |
+
if (local_idx < TILE_COLS && fused_global_col < N) {
|
| 263 |
+
{% for oc in range(projectionOutChannels) %}
|
| 264 |
+
var projected_acc{{ oc }} = f32(0.0);
|
| 265 |
+
{% endfor %}
|
| 266 |
+
for (var channel = 0u; channel < M; channel = channel + 1u) {
|
| 267 |
+
let producer_value = tile_B[channel * TILE_COLS + local_idx]{% if hasBias %} + f32(bias[channel]){% endif %};
|
| 268 |
+
let activated_value = producer_value;
|
| 269 |
+
{% for oc in range(projectionOutChannels) %}
|
| 270 |
+
projected_acc{{ oc }} = projected_acc{{ oc }} + activated_value * f32(projectionW[{{ oc }}u * M + channel]);
|
| 271 |
+
{% endfor %}
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
let fused_y_base = batch * {{ projectionOutChannels }}u * N + fused_global_col;
|
| 275 |
+
{% for oc in range(projectionOutChannels) %}
|
| 276 |
+
let projected{{ oc }} = projected_acc{{ oc }};
|
| 277 |
+
let activated{{ oc }} = projected{{ oc }};
|
| 278 |
+
y[fused_y_base + {{ oc }}u * N] = activated{{ oc }};
|
| 279 |
+
{% endfor %}
|
| 280 |
+
}
|
| 281 |
+
{% elif useDirectMatrixStore %}
|
| 282 |
+
{% if padded %}
|
| 283 |
+
// Padded N only needs guarded publication for the final partial-width tile.
|
| 284 |
+
// Full tiles can use the same direct collective stores as the aligned path;
|
| 285 |
+
// the branch is uniform because b_global_base depends only on workgroup_id.x.
|
| 286 |
+
if (b_global_base + TILE_COLS <= N) {
|
| 287 |
+
{% endif %}
|
| 288 |
+
// Full f32 output tiles can be published by the subgroup-matrix collectives
|
| 289 |
+
// directly. With no epilogue this removes scratch traffic and three barriers.
|
| 290 |
+
// When an epilogue is present, publish the raw accumulators first, then apply
|
| 291 |
+
// bias, residual Z, and activation cooperatively after one storage barrier.
|
| 292 |
+
let matrix_c_offset = c_base + (a_global_base + base_A) * N + b_global_base + base_B;
|
| 293 |
+
subgroupMatrixStore(&y, matrix_c_offset + 0u * N + 0u, matC00, false, N);
|
| 294 |
+
subgroupMatrixStore(&y, matrix_c_offset + 0u * N + 8u, matC01, false, N);
|
| 295 |
+
subgroupMatrixStore(&y, matrix_c_offset + 0u * N + 16u, matC02, false, N);
|
| 296 |
+
subgroupMatrixStore(&y, matrix_c_offset + 0u * N + 24u, matC03, false, N);
|
| 297 |
+
subgroupMatrixStore(&y, matrix_c_offset + 8u * N + 0u, matC10, false, N);
|
| 298 |
+
subgroupMatrixStore(&y, matrix_c_offset + 8u * N + 8u, matC11, false, N);
|
| 299 |
+
subgroupMatrixStore(&y, matrix_c_offset + 8u * N + 16u, matC12, false, N);
|
| 300 |
+
subgroupMatrixStore(&y, matrix_c_offset + 8u * N + 24u, matC13, false, N);
|
| 301 |
+
{% if hasBias or hasActivation or hasZ %}
|
| 302 |
+
storageBarrier();
|
| 303 |
+
for (var tile_idx = local_idx; tile_idx < TILE_ROWS * TILE_COLS; tile_idx += {{ workgroupThreadsValue }}u) {
|
| 304 |
+
let local_row = tile_idx / TILE_COLS;
|
| 305 |
+
let local_col = tile_idx % TILE_COLS;
|
| 306 |
+
let global_row = a_global_base + local_row;
|
| 307 |
+
let global_col = b_global_base + local_col;
|
| 308 |
+
let y_index = c_base + global_row * N + global_col;
|
| 309 |
+
var raw = f32(y[y_index]);
|
| 310 |
+
{% if hasBias %}
|
| 311 |
+
raw += f32(bias[global_row]);
|
| 312 |
+
{% endif %}
|
| 313 |
+
{% if hasActivation or hasZ %}
|
| 314 |
+
y[y_index] = epi(raw{% if hasZ %}, y_index{% endif %});
|
| 315 |
+
{% else %}
|
| 316 |
+
y[y_index] = {{ T }}(raw);
|
| 317 |
+
{% endif %}
|
| 318 |
+
}
|
| 319 |
+
{% endif %}
|
| 320 |
+
{% if padded %}
|
| 321 |
+
return;
|
| 322 |
+
}
|
| 323 |
+
{% endif %}
|
| 324 |
+
{% endif %}
|
| 325 |
+
{% if (not useDirectMatrixStore or padded) and not splitKPartial and not fusedNarrowProjection %}
|
| 326 |
+
// The four scratch banks are reused across the two row-groups, and each is written
|
| 327 |
+
// by a collective subgroupMatrixStore then read CROSS-LANE by storeOutput. Barriers
|
| 328 |
+
// give the reads visibility of the store AND stop the second row-group's store from
|
| 329 |
+
// clobbering the first's still-in-flight readback when a partial final M-tile
|
| 330 |
+
// diverges storeOutput's guard. Without both barriers the last valid row can be corrupted.
|
| 331 |
+
subgroupMatrixStore(&scratch[subtile_id][0], 0u, matC00, false, 8u);
|
| 332 |
+
subgroupMatrixStore(&scratch[subtile_id][1], 0u, matC01, false, 8u);
|
| 333 |
+
subgroupMatrixStore(&scratch[subtile_id][2], 0u, matC02, false, 8u);
|
| 334 |
+
subgroupMatrixStore(&scratch[subtile_id][3], 0u, matC03, false, 8u);
|
| 335 |
+
workgroupBarrier();
|
| 336 |
+
let row = sg_id / 4u;
|
| 337 |
+
let col = (sg_id % 4u) * 2u;
|
| 338 |
+
let row_base = a_global_base + base_A;
|
| 339 |
+
var matrix_c_offset = c_base + row_base * N + b_global_base + base_B;
|
| 340 |
+
var row_limit = i32(M) - i32(row_base);
|
| 341 |
+
storeOutput(matrix_c_offset, {% if hasBias or polyphase %}row_base, {% endif %}row, col, subtile_id, row_limit{% if padded %}, b_global_base + base_B{% endif %});
|
| 342 |
+
workgroupBarrier();
|
| 343 |
+
|
| 344 |
+
subgroupMatrixStore(&scratch[subtile_id][0], 0u, matC10, false, 8u);
|
| 345 |
+
subgroupMatrixStore(&scratch[subtile_id][1], 0u, matC11, false, 8u);
|
| 346 |
+
subgroupMatrixStore(&scratch[subtile_id][2], 0u, matC12, false, 8u);
|
| 347 |
+
subgroupMatrixStore(&scratch[subtile_id][3], 0u, matC13, false, 8u);
|
| 348 |
+
workgroupBarrier();
|
| 349 |
+
matrix_c_offset = matrix_c_offset + 8u * N;
|
| 350 |
+
row_limit = i32(M) - i32(row_base + 8u);
|
| 351 |
+
storeOutput(matrix_c_offset, {% if hasBias or polyphase %}row_base + 8u, {% endif %}row, col, subtile_id, row_limit{% if padded %}, b_global_base + base_B{% endif %});
|
| 352 |
+
{% endif %}
|
| 353 |
+
}
|
build/webgpu/conv-direct-nd.wgsl.jinja
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Direct {{ source.spatialRank }}D 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 source.usesF16 %}
|
| 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 |
+
fn fused_act(v: f32) -> f32 {
|
| 13 |
+
{% if activation == "Relu" %}
|
| 14 |
+
return max(v, 0.0);
|
| 15 |
+
{% elif activation == "Clip" %}
|
| 16 |
+
return clamp(v, f32({{ actAlpha }}), f32({{ actBeta }}));
|
| 17 |
+
{% elif activation == "LeakyRelu" %}
|
| 18 |
+
return select(v * f32({{ actAlpha }}), v, v >= 0.0);
|
| 19 |
+
{% elif activation == "Sigmoid" %}
|
| 20 |
+
return 1.0 / (1.0 + exp(-v));
|
| 21 |
+
{% elif activation == "Tanh" %}
|
| 22 |
+
return tanh(v);
|
| 23 |
+
{% elif activation == "HardSigmoid" %}
|
| 24 |
+
return clamp(f32({{ actAlpha }}) * v + f32({{ actBeta }}), 0.0, 1.0);
|
| 25 |
+
{% else %}
|
| 26 |
+
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 27 |
+
{% endif %}
|
| 28 |
+
}
|
| 29 |
+
{% endif %}
|
| 30 |
+
|
| 31 |
+
const WG: u32 = {{ convWorkgroupSize }}u;
|
| 32 |
+
|
| 33 |
+
@compute @workgroup_size(WG)
|
| 34 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>,
|
| 35 |
+
@builtin(num_workgroups) nwg: vec3<u32>) {
|
| 36 |
+
let index = gid.x + gid.y * nwg.x * WG;
|
| 37 |
+
if (index >= params.count) {
|
| 38 |
+
return;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
let ow = index % params.outW;
|
| 42 |
+
var q = index / params.outW;
|
| 43 |
+
let oh = q % params.outH;
|
| 44 |
+
q /= params.outH;
|
| 45 |
+
let od = q % params.outD;
|
| 46 |
+
q /= params.outD;
|
| 47 |
+
let oc = q % params.outChannels;
|
| 48 |
+
let batch = q / params.outChannels;
|
| 49 |
+
let group = oc / params.outChannelsPerGroup;
|
| 50 |
+
|
| 51 |
+
var acc = 0.0;
|
| 52 |
+
{% if source.hasBias %}
|
| 53 |
+
acc = f32(bias[oc]);
|
| 54 |
+
{% endif %}
|
| 55 |
+
for (var ic = 0u; ic < params.weightInChannels; ic += 1u) {
|
| 56 |
+
let inputChannel = group * params.inChannelsPerGroup + ic;
|
| 57 |
+
for (var kd = 0u; kd < params.kernelD; kd += 1u) {
|
| 58 |
+
let id = i32(od * params.strideD + kd * params.dilationD) - params.padD;
|
| 59 |
+
if (id < 0 || id >= i32(params.inD)) {
|
| 60 |
+
continue;
|
| 61 |
+
}
|
| 62 |
+
for (var kh = 0u; kh < params.kernelH; kh += 1u) {
|
| 63 |
+
let ih = i32(oh * params.strideH + kh * params.dilationH) - params.padH;
|
| 64 |
+
if (ih < 0 || ih >= i32(params.inH)) {
|
| 65 |
+
continue;
|
| 66 |
+
}
|
| 67 |
+
for (var kw = 0u; kw < params.kernelW; kw += 1u) {
|
| 68 |
+
let iw = i32(ow * params.strideW + kw * params.dilationW) - params.padW;
|
| 69 |
+
if (iw < 0 || iw >= i32(params.inW)) {
|
| 70 |
+
continue;
|
| 71 |
+
}
|
| 72 |
+
let xIndex = (((batch * params.inChannels + inputChannel) * params.inD
|
| 73 |
+
+ u32(id)) * params.inH + u32(ih)) * params.inW + u32(iw);
|
| 74 |
+
let wIndex = (((oc * params.weightInChannels + ic) * params.kernelD
|
| 75 |
+
+ kd) * params.kernelH + kh) * params.kernelW + kw;
|
| 76 |
+
acc += f32(x[xIndex]) * f32(w[wIndex]);
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
{% if hasZ %}
|
| 82 |
+
// Optional residual epilogue: Y = activation(conv + B + Z); Z shares Y's layout.
|
| 83 |
+
acc = acc + f32(zResidual[index]);
|
| 84 |
+
{% endif %}
|
| 85 |
+
y[index] = {{ T }}({% if hasActivation %}fused_act(acc){% else %}acc{% endif %});
|
| 86 |
+
}
|
build/webgpu/conv-direct-unrolled.wgsl.jinja
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 per output element, same flat index unpack and the same
|
| 4 |
+
// accumulation order (ic outer, then kh, kw ascending) as the
|
| 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 |
+
// Supports grouped convolution exactly like the scalar fallback. f16 inputs
|
| 9 |
+
// are widened to an f32 accumulator and narrowed once at store.
|
| 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 |
+
fn fused_act(v: f32) -> f32 {
|
| 19 |
+
{% if activation == "Relu" %}
|
| 20 |
+
return max(v, 0.0);
|
| 21 |
+
{% elif activation == "Clip" %}
|
| 22 |
+
return clamp(v, f32({{ actAlpha }}), f32({{ actBeta }}));
|
| 23 |
+
{% elif activation == "LeakyRelu" %}
|
| 24 |
+
return select(v * f32({{ actAlpha }}), v, v >= 0.0);
|
| 25 |
+
{% elif activation == "Sigmoid" %}
|
| 26 |
+
return 1.0 / (1.0 + exp(-v));
|
| 27 |
+
{% elif activation == "Tanh" %}
|
| 28 |
+
return tanh(v);
|
| 29 |
+
{% elif activation == "HardSigmoid" %}
|
| 30 |
+
return clamp(f32({{ actAlpha }}) * v + f32({{ actBeta }}), 0.0, 1.0);
|
| 31 |
+
{% else %}
|
| 32 |
+
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 33 |
+
{% endif %}
|
| 34 |
+
}
|
| 35 |
+
{% endif %}
|
| 36 |
+
|
| 37 |
+
const KERNEL_AREA: u32 = {{ source.kernelH * source.kernelW }}u;
|
| 38 |
+
const STRIDE_H: u32 = {{ source.strideH }}u;
|
| 39 |
+
const STRIDE_W: u32 = {{ source.strideW }}u;
|
| 40 |
+
const PAD_TOP: i32 = {{ source.padTop }};
|
| 41 |
+
const PAD_LEFT: i32 = {{ source.padLeft }};
|
| 42 |
+
|
| 43 |
+
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 44 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
|
| 45 |
+
// 2D-folded flat index: gid.y carries the high bits past the
|
| 46 |
+
// maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
|
| 47 |
+
let index = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
|
| 48 |
+
if (index >= params.count) {
|
| 49 |
+
return;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
let ow = index % params.outW;
|
| 53 |
+
var t = index / params.outW;
|
| 54 |
+
let oh = t % params.outH;
|
| 55 |
+
t = t / params.outH;
|
| 56 |
+
let oc = t % params.outChannels;
|
| 57 |
+
let batch = t / params.outChannels;
|
| 58 |
+
let group = oc / params.outChannelsPerGroup;
|
| 59 |
+
|
| 60 |
+
let ihBase = i32(oh * STRIDE_H) - PAD_TOP;
|
| 61 |
+
let iwBase = i32(ow * STRIDE_W) - PAD_LEFT;
|
| 62 |
+
let xChannelStride = params.inH * params.inW;
|
| 63 |
+
var xBase = (batch * params.inChannels + group * params.inChannelsPerGroup) * xChannelStride;
|
| 64 |
+
var wBase = oc * params.weightInChannels * KERNEL_AREA;
|
| 65 |
+
|
| 66 |
+
var acc = 0.0;
|
| 67 |
+
{% if hasBias %}
|
| 68 |
+
acc = f32(bias[oc]);
|
| 69 |
+
{% endif %}
|
| 70 |
+
for (var ic = 0u; ic < params.weightInChannels; ic = ic + 1u) {
|
| 71 |
+
{% for kh in range(source.kernelH) %}
|
| 72 |
+
{
|
| 73 |
+
let ih = ihBase + {{ kh * source.dilationH }}i;
|
| 74 |
+
if (ih >= 0 && ih < i32(params.inH)) {
|
| 75 |
+
let xRow = xBase + u32(ih) * params.inW;
|
| 76 |
+
{% for kw in range(source.kernelW) %}
|
| 77 |
+
{
|
| 78 |
+
let iw = iwBase + {{ kw * source.dilationW }}i;
|
| 79 |
+
if (iw >= 0 && iw < i32(params.inW)) {
|
| 80 |
+
acc = acc + f32(x[xRow + u32(iw)]) * f32(w[wBase + {{ kh * source.kernelW + kw }}u]);
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
{% endfor %}
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
{% endfor %}
|
| 87 |
+
xBase = xBase + xChannelStride;
|
| 88 |
+
wBase = wBase + KERNEL_AREA;
|
| 89 |
+
}
|
| 90 |
+
{% if hasZ %}
|
| 91 |
+
// Optional residual epilogue: Y = activation(conv + B + Z); Z shares Y's layout.
|
| 92 |
+
acc = acc + f32(zResidual[index]);
|
| 93 |
+
{% endif %}
|
| 94 |
+
y[index] = {{ T }}({% if hasActivation %}fused_act(acc){% else %}acc{% endif %});
|
| 95 |
+
}
|
build/webgpu/conv-im2col-nchw.wgsl.jinja
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Materialize the convolution column matrix
|
| 2 |
+
// cols[b][k][pos] = X[b, ic, oh*strideH + kh*dilationH - padTop,
|
| 3 |
+
// ow*strideW + kw*dilationW - padLeft]
|
| 4 |
+
// with zero outside the input and k = (ic*KH+kh)*KW+kw. This matches the OIHW
|
| 5 |
+
// weight flattening, so the following GEMM consumes [M,K] x [K,outH*outW].
|
| 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 |
+
{% if usesF16 %}
|
| 10 |
+
enable f16;
|
| 11 |
+
{% endif %}
|
| 12 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 13 |
+
|
| 14 |
+
const KERNEL_H: u32 = {{ source.kernelH }}u;
|
| 15 |
+
const KERNEL_W: u32 = {{ source.kernelW }}u;
|
| 16 |
+
const KSIZE: u32 = KERNEL_H * KERNEL_W;
|
| 17 |
+
const STRIDE_H: u32 = {{ source.strideH }}u;
|
| 18 |
+
const STRIDE_W: u32 = {{ source.strideW }}u;
|
| 19 |
+
const DILATION_H: u32 = {{ source.dilationH }}u;
|
| 20 |
+
const DILATION_W: u32 = {{ source.dilationW }}u;
|
| 21 |
+
const PAD_TOP: i32 = {{ source.padTop }};
|
| 22 |
+
const PAD_LEFT: i32 = {{ source.padLeft }};
|
| 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
|
| 26 |
+
// columns >= outCount zero-filled, so every GEMM tile load is full and the zero
|
| 27 |
+
// region contributes exact zeros to the dot products / is never stored back.
|
| 28 |
+
const COLS_STRIDE: u32 = {{ nPadded }}u;
|
| 29 |
+
const K_PADDED: u32 = {{ kPadded }}u;
|
| 30 |
+
|
| 31 |
+
{% endif %}
|
| 32 |
+
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 33 |
+
fn main(
|
| 34 |
+
@builtin(global_invocation_id) gid: vec3<u32>,
|
| 35 |
+
@builtin(num_workgroups) nwg: vec3<u32>
|
| 36 |
+
) {
|
| 37 |
+
let k = gid.y;
|
| 38 |
+
let batch = gid.z;
|
| 39 |
+
// Loop-invariant: these depend on the k row alone.
|
| 40 |
+
let ic = k / KSIZE;
|
| 41 |
+
let kq = k % KSIZE;
|
| 42 |
+
let kh = kq / KERNEL_W;
|
| 43 |
+
let kw = kq % KERNEL_W;
|
| 44 |
+
let planeBase = (batch * params.inChannels + ic) * params.inH;
|
| 45 |
+
{% if padded %}
|
| 46 |
+
let limit = COLS_STRIDE;
|
| 47 |
+
{% else %}
|
| 48 |
+
let limit = params.outCount;
|
| 49 |
+
{% endif %}
|
| 50 |
+
let step = nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
|
| 51 |
+
var pos = gid.x;
|
| 52 |
+
loop {
|
| 53 |
+
if (pos >= limit) { break; }
|
| 54 |
+
var value = 0.0;
|
| 55 |
+
{% if padded %}
|
| 56 |
+
if (pos < params.outCount && k < params.kRows) {
|
| 57 |
+
{% endif %}
|
| 58 |
+
let oh = pos / params.outW;
|
| 59 |
+
let ow = pos % params.outW;
|
| 60 |
+
let ih = i32(oh * STRIDE_H + kh * DILATION_H) - PAD_TOP;
|
| 61 |
+
let iw = i32(ow * STRIDE_W + kw * DILATION_W) - PAD_LEFT;
|
| 62 |
+
if (ih >= 0 && ih < i32(params.inH) && iw >= 0 && iw < i32(params.inW)) {
|
| 63 |
+
value = f32(x[(planeBase + u32(ih)) * params.inW + u32(iw)]);
|
| 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 |
+
}
|
build/webgpu/conv1d-tiled-reg.wgsl.jinja
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Implicit-GEMM register-tiled 1-D convolution over NCW: Y[b] (M x N) =
|
| 2 |
+
// W (M x K) @ im2col(X[b]) (K x N) with M = outChannels, K = inChannels *
|
| 3 |
+
// kernelW, N = outW. The im2col operand is never materialized: the X staging
|
| 4 |
+
// loop decodes each GEMM row into (ic, kw) and samples x with stride /
|
| 5 |
+
// dilation / left-pad applied, so one program covers general stride, padding,
|
| 6 |
+
// and dilation (group == 1 only). Workgroup geometry is tunable: a WG_X x WG_Y
|
| 7 |
+
// workgroup computes a (WG_Y*TM) x (WG_X*TN) output tile from BK-deep staged
|
| 8 |
+
// slices, each thread owning a TM x TN register micro-tile. Cooperative loads
|
| 9 |
+
// and stores are fully bounds-checked, so partial tiles on any edge stay
|
| 10 |
+
// correct for every tunable setting. f32 accumulation; bias and the optional
|
| 11 |
+
// activation epilogue are applied in the accumulator domain at
|
| 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 |
+
fn fused_act(v: f32) -> f32 {
|
| 28 |
+
{% if activation == "Relu" %}
|
| 29 |
+
return max(v, 0.0);
|
| 30 |
+
{% elif activation == "Clip" %}
|
| 31 |
+
return clamp(v, f32({{ actAlpha }}), f32({{ actBeta }}));
|
| 32 |
+
{% elif activation == "LeakyRelu" %}
|
| 33 |
+
return select(v * f32({{ actAlpha }}), v, v >= 0.0);
|
| 34 |
+
{% elif activation == "Sigmoid" %}
|
| 35 |
+
return 1.0 / (1.0 + exp(-v));
|
| 36 |
+
{% elif activation == "Tanh" %}
|
| 37 |
+
return tanh(v);
|
| 38 |
+
{% elif activation == "HardSigmoid" %}
|
| 39 |
+
return clamp(f32({{ actAlpha }}) * v + f32({{ actBeta }}), 0.0, 1.0);
|
| 40 |
+
{% else %}
|
| 41 |
+
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 42 |
+
{% endif %}
|
| 43 |
+
}
|
| 44 |
+
{% endif %}
|
| 45 |
+
|
| 46 |
+
{% set tileT = "f16" if usesF16 else "f32" %}
|
| 47 |
+
|
| 48 |
+
const WG_X: u32 = {{ convWgX }}u;
|
| 49 |
+
const WG_Y: u32 = {{ convWgY }}u;
|
| 50 |
+
const THREADS: u32 = WG_X * WG_Y;
|
| 51 |
+
const BK: u32 = {{ convKTile }}u;
|
| 52 |
+
const TM: u32 = {{ convTileM }}u;
|
| 53 |
+
const TN: u32 = {{ convTileN }}u;
|
| 54 |
+
const BM: u32 = WG_Y * TM;
|
| 55 |
+
const BN: u32 = WG_X * TN;
|
| 56 |
+
|
| 57 |
+
fn store_out(oc: u32, ow: u32, yBase: u32, raw: f32) {
|
| 58 |
+
if (oc >= params.outChannels || ow >= params.outW) {
|
| 59 |
+
return;
|
| 60 |
+
}
|
| 61 |
+
var v = raw;
|
| 62 |
+
{% if hasBias %}
|
| 63 |
+
v = v + f32(bias[oc]);
|
| 64 |
+
{% endif %}
|
| 65 |
+
{% if hasActivation %}
|
| 66 |
+
v = fused_act(v);
|
| 67 |
+
{% endif %}
|
| 68 |
+
y[yBase + oc * params.outW + ow] = {{ T }}(v);
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
const K_VECS: u32 = BK / 4u;
|
| 72 |
+
var<workgroup> tileW: array<array<vec4<{{ tileT }}>, K_VECS>, BM>; // W[oc][k/4]
|
| 73 |
+
var<workgroup> tileX: array<array<vec4<{{ tileT }}>, K_VECS>, BN>; // im2col[ow][k/4]
|
| 74 |
+
|
| 75 |
+
@compute @workgroup_size(WG_X, WG_Y, 1)
|
| 76 |
+
fn main(
|
| 77 |
+
@builtin(workgroup_id) wg: vec3<u32>,
|
| 78 |
+
@builtin(local_invocation_id) lid: vec3<u32>
|
| 79 |
+
) {
|
| 80 |
+
let K = params.inChannels * params.kernelW;
|
| 81 |
+
let ocBase = wg.y * BM;
|
| 82 |
+
let owBase = wg.x * BN;
|
| 83 |
+
let batch = wg.z;
|
| 84 |
+
let li = lid.y * WG_X + lid.x;
|
| 85 |
+
|
| 86 |
+
{% for i in range(convTileM) %}
|
| 87 |
+
{% for j in range(convTileN) %}
|
| 88 |
+
var acc{{ i }}_{{ j }}: f32 = 0.0;
|
| 89 |
+
{% endfor %}
|
| 90 |
+
{% endfor %}
|
| 91 |
+
|
| 92 |
+
let numTiles = (K + BK - 1u) / BK;
|
| 93 |
+
for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
|
| 94 |
+
let kBase = kt * BK;
|
| 95 |
+
// One vector word per lane per pass: four contiguous K values of one output
|
| 96 |
+
// channel, and the four K taps of one output position, whose (ic, kw) is
|
| 97 |
+
// decoded once and then walked.
|
| 98 |
+
for (var idx: u32 = li; idx < BM * K_VECS; idx = idx + THREADS) {
|
| 99 |
+
let wr = idx / K_VECS;
|
| 100 |
+
let wc4 = idx % K_VECS;
|
| 101 |
+
let oc = ocBase + wr;
|
| 102 |
+
let k = kBase + wc4 * 4u;
|
| 103 |
+
var word = vec4<{{ tileT }}>({{ tileT }}(0.0));
|
| 104 |
+
if (oc < params.outChannels) {
|
| 105 |
+
let rowBase = oc * K + k;
|
| 106 |
+
{% for component in range(4) %}
|
| 107 |
+
if (k + {{ component }}u < K) { word[{{ component }}u] = {{ tileT }}(w[rowBase + {{ component }}u]); }
|
| 108 |
+
{% endfor %}
|
| 109 |
+
}
|
| 110 |
+
tileW[wr][wc4] = word;
|
| 111 |
+
}
|
| 112 |
+
for (var idx: u32 = li; idx < BN * K_VECS; idx = idx + THREADS) {
|
| 113 |
+
let xc = idx / K_VECS;
|
| 114 |
+
let xr4 = idx % K_VECS;
|
| 115 |
+
let ow = owBase + xc;
|
| 116 |
+
let k0 = kBase + xr4 * 4u;
|
| 117 |
+
var word = vec4<{{ tileT }}>({{ tileT }}(0.0));
|
| 118 |
+
if (ow < params.outW) {
|
| 119 |
+
let owStride = ow * params.strideW;
|
| 120 |
+
let planeBase = batch * params.inChannels;
|
| 121 |
+
var ic = k0 / params.kernelW;
|
| 122 |
+
var kw = k0 % params.kernelW;
|
| 123 |
+
{% for component in range(4) %}
|
| 124 |
+
if (k0 + {{ component }}u < K) {
|
| 125 |
+
let iw{{ component }} = i32(owStride + kw * params.dilationW) - params.padW;
|
| 126 |
+
if (iw{{ component }} >= 0 && iw{{ component }} < i32(params.inW)) {
|
| 127 |
+
word[{{ component }}u] = {{ tileT }}(x[(planeBase + ic) * params.inW + u32(iw{{ component }})]);
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
kw = kw + 1u;
|
| 131 |
+
if (kw == params.kernelW) {
|
| 132 |
+
kw = 0u;
|
| 133 |
+
ic = ic + 1u;
|
| 134 |
+
}
|
| 135 |
+
{% endfor %}
|
| 136 |
+
}
|
| 137 |
+
tileX[xc][xr4] = word;
|
| 138 |
+
}
|
| 139 |
+
workgroupBarrier();
|
| 140 |
+
let tileOc = lid.y * TM;
|
| 141 |
+
let tileOw = lid.x * TN;
|
| 142 |
+
for (var kv: u32 = 0u; kv < K_VECS; kv = kv + 1u) {
|
| 143 |
+
{% for i in range(convTileM) %}
|
| 144 |
+
let wv{{ i }} = vec4<f32>(tileW[tileOc + {{ i }}u][kv]);
|
| 145 |
+
{% endfor %}
|
| 146 |
+
{% for j in range(convTileN) %}
|
| 147 |
+
let xv{{ j }} = vec4<f32>(tileX[tileOw + {{ j }}u][kv]);
|
| 148 |
+
{% endfor %}
|
| 149 |
+
{% for i in range(convTileM) %}
|
| 150 |
+
{% for j in range(convTileN) %}
|
| 151 |
+
acc{{ i }}_{{ j }} = acc{{ i }}_{{ j }} + dot(wv{{ i }}, xv{{ j }});
|
| 152 |
+
{% endfor %}
|
| 153 |
+
{% endfor %}
|
| 154 |
+
}
|
| 155 |
+
workgroupBarrier();
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
let yBase = batch * params.outChannels * params.outW;
|
| 159 |
+
let oc0 = ocBase + lid.y * TM;
|
| 160 |
+
let ow0 = owBase + lid.x * TN;
|
| 161 |
+
{% for i in range(convTileM) %}
|
| 162 |
+
{% for j in range(convTileN) %}
|
| 163 |
+
store_out(oc0 + {{ i }}u, ow0 + {{ j }}u, yBase, acc{{ i }}_{{ j }});
|
| 164 |
+
{% endfor %}
|
| 165 |
+
{% endfor %}
|
| 166 |
+
}
|
build/webgpu/conv2d-grouped-large-w4.wgsl.jinja
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
fn fused_act(v: f32) -> f32 {
|
| 10 |
+
{% if activation == "Relu" %}
|
| 11 |
+
return max(v, 0.0);
|
| 12 |
+
{% elif activation == "Clip" %}
|
| 13 |
+
return clamp(v, f32({{ actAlpha }}), f32({{ actBeta }}));
|
| 14 |
+
{% elif activation == "LeakyRelu" %}
|
| 15 |
+
return select(v * f32({{ actAlpha }}), v, v >= 0.0);
|
| 16 |
+
{% elif activation == "Sigmoid" %}
|
| 17 |
+
return 1.0 / (1.0 + exp(-v));
|
| 18 |
+
{% elif activation == "Tanh" %}
|
| 19 |
+
return tanh(v);
|
| 20 |
+
{% elif activation == "HardSigmoid" %}
|
| 21 |
+
return clamp(f32({{ actAlpha }}) * v + f32({{ actBeta }}), 0.0, 1.0);
|
| 22 |
+
{% else %}
|
| 23 |
+
return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
|
| 24 |
+
{% endif %}
|
| 25 |
+
}
|
| 26 |
+
{% endif %}
|
| 27 |
+
|
| 28 |
+
// Large grouped kernels are dominated by repeatedly fetching the same filter
|
| 29 |
+
// value for neighboring output columns and input window for neighboring output
|
| 30 |
+
// channels. One invocation computes four adjacent columns for OC_TILE channels,
|
| 31 |
+
// sharing each input load across those channels. Shape and window geometry are
|
| 32 |
+
// static, so all indexing divisors and kernel offsets are strength-reduced and
|
| 33 |
+
// unrolled by the shader compiler.
|
| 34 |
+
const COUNT_TILES: u32 = {{ source.countTiles }}u;
|
| 35 |
+
{% if source.tailOutput %}const OUT_W: u32 = max(1u, {{ source.outW }}u);
|
| 36 |
+
{% endif %}
|
| 37 |
+
const OUT_W4: u32 = max(1u, {{ source.outW4 }}u);
|
| 38 |
+
const OUT_H: u32 = max(1u, {{ source.outH }}u);
|
| 39 |
+
const OUT_C: u32 = max(1u, {{ source.outC }}u);
|
| 40 |
+
const OC_TILE: u32 = {{ source.ocTile }}u;
|
| 41 |
+
const OUT_C_TILES: u32 = OUT_C / OC_TILE;
|
| 42 |
+
const OUT_CPG: u32 = {{ source.outCPerGroup }}u;
|
| 43 |
+
const IN_C: u32 = {{ source.inC }}u;
|
| 44 |
+
const IN_CPG: u32 = {{ source.inCPerGroup }}u;
|
| 45 |
+
const IN_H: i32 = {{ source.inH }};
|
| 46 |
+
const IN_W: i32 = {{ source.inW }};
|
| 47 |
+
const IN_H_U: u32 = {{ source.inH }}u;
|
| 48 |
+
const IN_W_U: u32 = {{ source.inW }}u;
|
| 49 |
+
const IN_PLANE: u32 = IN_H_U * IN_W_U;
|
| 50 |
+
{% if not source.tailOutput %}const OUT_PLANE4: u32 = OUT_H * OUT_W4;
|
| 51 |
+
{% endif %}
|
| 52 |
+
const KAREA: u32 = {{ source.kernelH * source.kernelW }}u;
|
| 53 |
+
const STRIDE_H: u32 = {{ source.strideH }}u;
|
| 54 |
+
const STRIDE_W: i32 = {{ source.strideW }};
|
| 55 |
+
const PAD_TOP: i32 = {{ source.padTop }};
|
| 56 |
+
const PAD_LEFT: i32 = {{ source.padLeft }};
|
| 57 |
+
const WG: u32 = {{ source.workgroupSize }}u;
|
| 58 |
+
|
| 59 |
+
@compute @workgroup_size({{ source.workgroupSize }})
|
| 60 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
|
| 61 |
+
let q = gid.x + gid.y * nwg.x * WG;
|
| 62 |
+
if (q >= COUNT_TILES) { return; }
|
| 63 |
+
|
| 64 |
+
let ow4 = q % OUT_W4;
|
| 65 |
+
var t = q / OUT_W4;
|
| 66 |
+
let oh = t % OUT_H;
|
| 67 |
+
t /= OUT_H;
|
| 68 |
+
let ocBase = (t % OUT_C_TILES) * OC_TILE;
|
| 69 |
+
let batch = t / OUT_C_TILES;
|
| 70 |
+
let group = ocBase / OUT_CPG;
|
| 71 |
+
let ihBase = i32(oh * STRIDE_H) - PAD_TOP;
|
| 72 |
+
let iwBase = i32(ow4 * 4u) * STRIDE_W - PAD_LEFT;
|
| 73 |
+
var xChannelBase = (batch * IN_C + group * IN_CPG) * IN_PLANE;
|
| 74 |
+
{% for oct in range(source.ocTile) %}
|
| 75 |
+
var wBase{{ oct }} = (ocBase + {{ oct }}u) * IN_CPG * KAREA;
|
| 76 |
+
var acc{{ oct }} = vec4<f32>(0.0);
|
| 77 |
+
{% endfor %}
|
| 78 |
+
|
| 79 |
+
for (var ic = 0u; ic < IN_CPG; ic++) {
|
| 80 |
+
{% for kh in range(source.kernelH) %}
|
| 81 |
+
{
|
| 82 |
+
let ih = ihBase + {{ kh * source.dilationH }};
|
| 83 |
+
if (ih >= 0 && ih < IN_H) {
|
| 84 |
+
let xRow = xChannelBase + u32(ih) * IN_W_U;
|
| 85 |
+
{% if source.span <= source.spanCap %}
|
| 86 |
+
// The four output columns and the kernel taps overlap heavily: tap kw of
|
| 87 |
+
// lane l reads input column kw*dilationW + l*strideW, so kernelW*4 reads
|
| 88 |
+
// land on only SPAN distinct columns. Load the row window once and let
|
| 89 |
+
// every tap take its value from a register. Out-of-range columns hold
|
| 90 |
+
// zero, which contributes nothing to the accumulation exactly as the
|
| 91 |
+
// per-tap bounds test did.
|
| 92 |
+
//
|
| 93 |
+
// SPAN grows as (kernelW-1)*dilationW, so a widely dilated kernel would
|
| 94 |
+
// want more registers than a thread has and spill. Past spanCap the
|
| 95 |
+
// per-tap form below stays, where the register cost is fixed.
|
| 96 |
+
{% for s in range(source.span) %}
|
| 97 |
+
{% set used = namespace(value=false) %}
|
| 98 |
+
{% for kw in range(source.kernelW) %}
|
| 99 |
+
{% for lane in range(4) %}
|
| 100 |
+
{% if s == kw * source.dilationW + lane * source.strideW %}{% set used.value = true %}{% endif %}
|
| 101 |
+
{% endfor %}
|
| 102 |
+
{% endfor %}
|
| 103 |
+
{% if used.value %}
|
| 104 |
+
var xw{{ s }} = 0.0;
|
| 105 |
+
{% endif %}
|
| 106 |
+
{% endfor %}
|
| 107 |
+
{% for s in range(source.span) %}
|
| 108 |
+
{% set used = namespace(value=false) %}
|
| 109 |
+
{% for kw in range(source.kernelW) %}
|
| 110 |
+
{% for lane in range(4) %}
|
| 111 |
+
{% if s == kw * source.dilationW + lane * source.strideW %}{% set used.value = true %}{% endif %}
|
| 112 |
+
{% endfor %}
|
| 113 |
+
{% endfor %}
|
| 114 |
+
{% if used.value %}
|
| 115 |
+
{
|
| 116 |
+
let iw = iwBase + {{ s }};
|
| 117 |
+
if (iw >= 0 && iw < IN_W) { xw{{ s }} = f32(x[xRow + u32(iw)]); }
|
| 118 |
+
}
|
| 119 |
+
{% endif %}
|
| 120 |
+
{% endfor %}
|
| 121 |
+
{% for kw in range(source.kernelW) %}
|
| 122 |
+
{
|
| 123 |
+
{% for oct in range(source.ocTile) %}
|
| 124 |
+
let weight{{ oct }} = f32(w[wBase{{ oct }} + {{ kh * source.kernelW + kw }}u]);
|
| 125 |
+
{% endfor %}
|
| 126 |
+
{% for lane in range(4) %}
|
| 127 |
+
{% for oct in range(source.ocTile) %}
|
| 128 |
+
acc{{ oct }}.{{ ["x", "y", "z", "w"][lane] }} += xw{{ kw * source.dilationW + lane * source.strideW }} * weight{{ oct }};
|
| 129 |
+
{% endfor %}
|
| 130 |
+
{% endfor %}
|
| 131 |
+
}
|
| 132 |
+
{% endfor %}
|
| 133 |
+
{% else %}
|
| 134 |
+
{% for kw in range(source.kernelW) %}
|
| 135 |
+
{
|
| 136 |
+
{% for oct in range(source.ocTile) %}
|
| 137 |
+
let weight{{ oct }} = f32(w[wBase{{ oct }} + {{ kh * source.kernelW + kw }}u]);
|
| 138 |
+
{% endfor %}
|
| 139 |
+
let iwK = iwBase + {{ kw * source.dilationW }};
|
| 140 |
+
{% for lane in range(4) %}
|
| 141 |
+
let iwLane{{ lane }} = iwK + {{ lane * source.strideW }};
|
| 142 |
+
if (iwLane{{ lane }} >= 0 && iwLane{{ lane }} < IN_W) {
|
| 143 |
+
let xValue = f32(x[xRow + u32(iwLane{{ lane }})]);
|
| 144 |
+
{% for oct in range(source.ocTile) %}
|
| 145 |
+
acc{{ oct }}.{{ ["x", "y", "z", "w"][lane] }} += xValue * weight{{ oct }};
|
| 146 |
+
{% endfor %}
|
| 147 |
+
}
|
| 148 |
+
{% endfor %}
|
| 149 |
+
}
|
| 150 |
+
{% endfor %}
|
| 151 |
+
{% endif %}
|
| 152 |
+
}
|
| 153 |
+
}
|
| 154 |
+
{% endfor %}
|
| 155 |
+
xChannelBase += IN_PLANE;
|
| 156 |
+
{% for oct in range(source.ocTile) %}
|
| 157 |
+
wBase{{ oct }} += KAREA;
|
| 158 |
+
{% endfor %}
|
| 159 |
+
}
|
| 160 |
+
{% if source.tailOutput %}
|
| 161 |
+
let outBase = ((batch * OUT_C + ocBase) * OUT_H + oh) * OUT_W + ow4 * 4u;
|
| 162 |
+
{% for oct in range(source.ocTile) %}
|
| 163 |
+
let outChannelBase{{ oct }} = outBase + {{ oct }}u * OUT_H * OUT_W;
|
| 164 |
+
{% for lane in range(4) %}
|
| 165 |
+
if (ow4 * 4u + {{ lane }}u < OUT_W) {
|
| 166 |
+
y[outChannelBase{{ oct }} + {{ lane }}u] = {{ scalar }}({% if hasActivation %}fused_act(acc{{ oct }}.{{ ["x", "y", "z", "w"][lane] }}){% else %}acc{{ oct }}.{{ ["x", "y", "z", "w"][lane] }}{% endif %});
|
| 167 |
+
}
|
| 168 |
+
{% endfor %}
|
| 169 |
+
{% endfor %}
|
| 170 |
+
{% else %}
|
| 171 |
+
let outBase = ((batch * OUT_C + ocBase) * OUT_H + oh) * OUT_W4 + ow4;
|
| 172 |
+
{% for oct in range(source.ocTile) %}
|
| 173 |
+
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 %});
|
| 174 |
+
{% endfor %}
|
| 175 |
+
{% endif %}
|
| 176 |
+
}
|
build/webgpu/manifest.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/webgpu/metadata.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "com.microsoft.FusedConv",
|
| 3 |
+
"id": "_com_microsoft_fusedconv_webgpu_947df1c",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "Apache-2.0",
|
| 6 |
+
"backend": { "type": "webgpu" },
|
| 7 |
+
"digest": {
|
| 8 |
+
"algorithm": "sha256",
|
| 9 |
+
"files": {
|
| 10 |
+
"bench.json": "0T4EaJworpji9PrXCF3znUUqiMBBbSgShzzAaM/dOWY=",
|
| 11 |
+
"conv-1x1-gemm-tiled-reg.wgsl.jinja": "bDk/sW4LxlVlqVJ6L/hpKGjfky1UKWwYL3C0fdVWjFA=",
|
| 12 |
+
"conv-1x1-gemm-tiled.wgsl.jinja": "eHdHLa3X2HriPxhMMSmzkVPelPg+sm4xDz9ZJ4qI0hA=",
|
| 13 |
+
"conv-1x1-subgroup-matrix.wgsl.jinja": "FTHq1ftW5Zk7as0fc8T+b0dvbQFQFmcX0AWARnICV28=",
|
| 14 |
+
"conv-direct-nd.wgsl.jinja": "yEas2jFgJNfM8NoHjv+4ZafMC1/nwl5MvgQeTFfOl4w=",
|
| 15 |
+
"conv-direct-unrolled.wgsl.jinja": "84nok2Ie1Lc91UrXiUEl1GFk+cwa4Fu0vEkhRyOmsxs=",
|
| 16 |
+
"conv-im2col-nchw.wgsl.jinja": "7IUuXo33elZx0pnR9M2vUXzXDjFuw2bJODkZViV/WQg=",
|
| 17 |
+
"conv1d-tiled-reg.wgsl.jinja": "d9H8LBgSgsqxBbg85o5dCMuOEIW/hOICoEi5gysrvTg=",
|
| 18 |
+
"conv2d-grouped-large-w4.wgsl.jinja": "jhLKpmvfkcFQPBXXznt3im++rtVwNNxUoRqqgHNpmDM=",
|
| 19 |
+
"manifest.json": "jMjmEiSVgMOjNheSacCaHlppVU7uWplUp2lv3fvGH1A=",
|
| 20 |
+
"test.json": "/KodhC9ZJ6IxO1wokLmeGH0zsOfDpds9+mjZLZRnPXw="
|
| 21 |
+
}
|
| 22 |
+
},
|
| 23 |
+
"provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
|
| 24 |
+
"webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.FusedConv" }
|
| 25 |
+
}
|
build/webgpu/test.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|