sync 91d990483a17
Browse files- README.md +31 -13
- build/webgpu/bench.json +54 -13
- build/webgpu/manifest.json +0 -0
- build/webgpu/matmul-nbits-dp4a-quantize.wgsl.jinja +1 -2
- build/webgpu/matmul-nbits-gemv-q4.wgsl.jinja +93 -47
- build/webgpu/matmul-nbits-q4-dp4a-prefill.wgsl.jinja +7 -4
- build/webgpu/matmul-nbits-q4-prefill-tile4x4.wgsl.jinja +105 -93
- build/webgpu/matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja +43 -22
- build/webgpu/matmul-nbits-q4-prefill-tiled.wgsl.jinja +11 -16
- build/webgpu/matmul-nbits-q4-sgmat.wgsl.jinja +29 -25
- build/webgpu/matmul-nbits.wgsl.jinja +11 -16
- build/webgpu/metadata.json +52 -14
- build/webgpu/reduce-axis0-splitk-combine.wgsl.jinja +122 -0
- build/webgpu/test.json +306 -40
README.md
CHANGED
|
@@ -18,19 +18,19 @@ See the [ONNX Runtime `MatMulNBits` contrib-operator spec](https://github.com/mi
|
|
| 18 |
|
| 19 |
## Inputs
|
| 20 |
|
| 21 |
-
| Name |
|
| 22 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
-
| `
|
| 24 |
-
| `
|
| 25 |
-
| `
|
| 26 |
-
| `
|
| 27 |
-
| `
|
| 28 |
|
| 29 |
## Outputs
|
| 30 |
|
| 31 |
-
| Name |
|
| 32 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 33 |
-
| `
|
| 34 |
|
| 35 |
## Attributes
|
| 36 |
|
|
@@ -38,10 +38,10 @@ Attributes and default values (overridable per request):
|
|
| 38 |
|
| 39 |
| Attribute | Default | Description |
|
| 40 |
| --- | --- | --- |
|
| 41 |
-
| `accuracy_level` | `0` | Minimum internal accuracy level: 0 (unset), 1 (float32), 2 (float16), 3 (bfloat16), or 4 (int8). |
|
| 42 |
-
| `bits` | `4` | Bit width used to quantize B; this package supports 2, 4, and 8. |
|
| 43 |
| `K` | — | Input feature dimension of the weight matrix. |
|
| 44 |
| `N` | — | Output feature dimension of the weight matrix. |
|
|
|
|
|
|
|
| 45 |
| `block_size` | — | Power-of-two quantization block size along K; it must be at least 16. |
|
| 46 |
|
| 47 |
## Type constraints
|
|
@@ -51,13 +51,26 @@ Attributes and default values (overridable per request):
|
|
| 51 |
| `T1` | `float32`, `float16` |
|
| 52 |
| `T3` | `float32`, `float16` |
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
## Device requirements
|
| 55 |
|
| 56 |
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.
|
| 57 |
|
| 58 |
## Files
|
| 59 |
|
| 60 |
-
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
|
| 61 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 62 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 63 |
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
|
@@ -69,13 +82,18 @@ Some implementation variants require `subgroup-matrix` and `subgroups`. These ar
|
|
| 69 |
- [`matmul-nbits-q4-prefill-tiled.wgsl.jinja`](build/webgpu/matmul-nbits-q4-prefill-tiled.wgsl.jinja)
|
| 70 |
- [`matmul-nbits-q4-sgmat.wgsl.jinja`](build/webgpu/matmul-nbits-q4-sgmat.wgsl.jinja)
|
| 71 |
- [`matmul-nbits.wgsl.jinja`](build/webgpu/matmul-nbits.wgsl.jinja)
|
|
|
|
| 72 |
|
| 73 |
## Use with `@huggingface/kernels`
|
| 74 |
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
|
|
|
| 79 |
|
| 80 |
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 81 |
|
|
|
|
| 18 |
|
| 19 |
## Inputs
|
| 20 |
|
| 21 |
+
| Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
|
| 22 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `aT` | `A` | `T1` | — | — | Float input matrix, not quantized. Rank 2 has shape `(M, K)` and rank 3 has shape `(batch, sequence, K)`; only the last axis is the reduction axis and the leading axes fold into the row count, so the ordinary activation needs no surrounding Reshape. | required |
|
| 24 |
+
| `bT` | `B` | `uint8` | `3` | — | Bit-packed uint8 weight matrix of shape `(N, k_blocks, blob_size)`, where `k_blocks = ceil(K / block_size)` and `blob_size = block_size * bits / 8`. Codes are packed low-first along K. Bound in the packed storage layout: four blob bytes per u32 word, so the kernels stream the blob's own bytes rather than one widened word per byte. | required |
|
| 25 |
+
| `scalesT` | `scales` | `T1` | `2` | — | Per-block dequantization scale factors of shape `(N, k_blocks)`, with the same dtype as `A`. | required |
|
| 26 |
+
| `zeroPointsT` | `zero_points` | `T3` | `2` | — | Standard unpacked per-block zero points with shape `(N, k_blocks)` and the same dtype as `A`. Omission uses `2^(bits - 1)`. | optional |
|
| 27 |
+
| `biasT` | `bias` | `T1` | `1` | — | Optional bias vector of shape `[N]` added to the output. | optional |
|
| 28 |
|
| 29 |
## Outputs
|
| 30 |
|
| 31 |
+
| Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
|
| 32 |
| --- | --- | --- | --- | --- | --- | --- |
|
| 33 |
+
| `yT` | `Y` | `T1` | same as `aT` | derived | Result of A multiplied by the dequantized weight matrix, with optional bias, same dtype and rank as A: the leading axes of A with a trailing N. | required |
|
| 34 |
|
| 35 |
## Attributes
|
| 36 |
|
|
|
|
| 38 |
|
| 39 |
| Attribute | Default | Description |
|
| 40 |
| --- | --- | --- |
|
|
|
|
|
|
|
| 41 |
| `K` | — | Input feature dimension of the weight matrix. |
|
| 42 |
| `N` | — | Output feature dimension of the weight matrix. |
|
| 43 |
+
| `accuracy_level` | `0` | Minimum internal accuracy level: 0 (unset), 1 (float32), 2 (float16), 3 (bfloat16), or 4 (int8). |
|
| 44 |
+
| `bits` | `4` | Bit width used to quantize B; this package supports 2, 4, and 8. |
|
| 45 |
| `block_size` | — | Power-of-two quantization block size along K; it must be at least 16. |
|
| 46 |
|
| 47 |
## Type constraints
|
|
|
|
| 51 |
| `T1` | `float32`, `float16` |
|
| 52 |
| `T3` | `float32`, `float16` |
|
| 53 |
|
| 54 |
+
## Implementation variants
|
| 55 |
+
|
| 56 |
+
One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
|
| 57 |
+
|
| 58 |
+
- `prefill_tiled_reg_vec4_splitk_default_zero` — Cuts the K reduction of the four-wide register-blocked prefill tile into power-of-two slices across dispatch.z, retaining at least 512 K values per slice, then sums the f32 partials and adds bias in a second pass.
|
| 59 |
+
- `prefill_tiled_reg_vec4_default_zero` — The register-blocked prefill tile with four-wide activation loads and a 128-row tile above 256 rows: the activation slice of every K tile is staged with one vector load per lane instead of four scalar loads, and the taller tile halves the dequantization work per multiply-add.
|
| 60 |
+
- `prefill_tiled_reg_vec4_splitk_zero_bias` — Cuts the K reduction of the four-wide register-blocked prefill tile into power-of-two slices across dispatch.z, retaining at least 512 K values per slice, then sums the f32 partials and adds bias in a second pass.
|
| 61 |
+
- `prefill_tiled_reg_vec4_zero_bias` — The register-blocked prefill tile with four-wide activation loads and a 128-row tile above 256 rows: the activation slice of every K tile is staged with one vector load per lane instead of four scalar loads, and the taller tile halves the dequantization work per multiply-add.
|
| 62 |
+
- `prefill_tiled_reg_vec4_splitk_zero_only` — Cuts the K reduction of the four-wide register-blocked prefill tile into power-of-two slices across dispatch.z, retaining at least 512 K values per slice, then sums the f32 partials and adds bias in a second pass.
|
| 63 |
+
- `prefill_tiled_reg_vec4_zero_only` — The register-blocked prefill tile with four-wide activation loads and a 128-row tile above 256 rows: the activation slice of every K tile is staged with one vector load per lane instead of four scalar loads, and the taller tile halves the dequantization work per multiply-add.
|
| 64 |
+
- `prefill_tiled_reg_vec4_splitk_bias_only` — Cuts the K reduction of the four-wide register-blocked prefill tile into power-of-two slices across dispatch.z, retaining at least 512 K values per slice, then sums the f32 partials and adds bias in a second pass.
|
| 65 |
+
- `prefill_tiled_reg_vec4_bias_only` — The register-blocked prefill tile with four-wide activation loads and a 128-row tile above 256 rows: the activation slice of every K tile is staged with one vector load per lane instead of four scalar loads, and the taller tile halves the dequantization work per multiply-add.
|
| 66 |
+
|
| 67 |
## Device requirements
|
| 68 |
|
| 69 |
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.
|
| 70 |
|
| 71 |
## Files
|
| 72 |
|
| 73 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
|
| 74 |
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 75 |
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 76 |
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
|
|
|
| 82 |
- [`matmul-nbits-q4-prefill-tiled.wgsl.jinja`](build/webgpu/matmul-nbits-q4-prefill-tiled.wgsl.jinja)
|
| 83 |
- [`matmul-nbits-q4-sgmat.wgsl.jinja`](build/webgpu/matmul-nbits-q4-sgmat.wgsl.jinja)
|
| 84 |
- [`matmul-nbits.wgsl.jinja`](build/webgpu/matmul-nbits.wgsl.jinja)
|
| 85 |
+
- [`reduce-axis0-splitk-combine.wgsl.jinja`](build/webgpu/reduce-axis0-splitk-combine.wgsl.jinja)
|
| 86 |
|
| 87 |
## Use with `@huggingface/kernels`
|
| 88 |
|
| 89 |
+
```sh
|
| 90 |
+
npm install --save-exact @huggingface/kernels@0.0.1-preview.2
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
|
| 94 |
|
| 95 |
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 96 |
+
It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
|
| 97 |
|
| 98 |
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 99 |
|
build/webgpu/bench.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
{
|
| 2 |
-
"op": "com.microsoft.MatMulNBits",
|
| 3 |
"tunableSpace": { "workgroupSize": [64, 128, 256] },
|
| 4 |
"cases": [
|
| 5 |
{
|
|
@@ -33,7 +32,7 @@
|
|
| 33 |
"metrics": [
|
| 34 |
{
|
| 35 |
"type": "bandwidth",
|
| 36 |
-
"value": "numel(shapes.bT)
|
| 37 |
}
|
| 38 |
]
|
| 39 |
},
|
|
@@ -71,7 +70,7 @@
|
|
| 71 |
"metrics": [
|
| 72 |
{
|
| 73 |
"type": "bandwidth",
|
| 74 |
-
"value": "numel(shapes.bT)
|
| 75 |
}
|
| 76 |
]
|
| 77 |
},
|
|
@@ -143,7 +142,7 @@
|
|
| 143 |
"metrics": [
|
| 144 |
{
|
| 145 |
"type": "bandwidth",
|
| 146 |
-
"value": "numel(shapes.bT)
|
| 147 |
}
|
| 148 |
]
|
| 149 |
},
|
|
@@ -215,7 +214,7 @@
|
|
| 215 |
"metrics": [
|
| 216 |
{
|
| 217 |
"type": "bandwidth",
|
| 218 |
-
"value": "numel(shapes.bT)
|
| 219 |
}
|
| 220 |
]
|
| 221 |
},
|
|
@@ -243,7 +242,7 @@
|
|
| 243 |
"metrics": [
|
| 244 |
{
|
| 245 |
"type": "bandwidth",
|
| 246 |
-
"value": "numel(shapes.bT)
|
| 247 |
}
|
| 248 |
]
|
| 249 |
},
|
|
@@ -336,7 +335,7 @@
|
|
| 336 |
"metrics": [
|
| 337 |
{
|
| 338 |
"type": "bandwidth",
|
| 339 |
-
"value": "numel(shapes.bT)
|
| 340 |
}
|
| 341 |
]
|
| 342 |
},
|
|
@@ -471,7 +470,7 @@
|
|
| 471 |
"metrics": [
|
| 472 |
{
|
| 473 |
"type": "bandwidth",
|
| 474 |
-
"value": "numel(shapes.bT)
|
| 475 |
}
|
| 476 |
]
|
| 477 |
},
|
|
@@ -500,7 +499,7 @@
|
|
| 500 |
"metrics": [
|
| 501 |
{
|
| 502 |
"type": "bandwidth",
|
| 503 |
-
"value": "numel(shapes.bT)
|
| 504 |
}
|
| 505 |
]
|
| 506 |
},
|
|
@@ -664,7 +663,7 @@
|
|
| 664 |
"metrics": [
|
| 665 |
{
|
| 666 |
"type": "bandwidth",
|
| 667 |
-
"value": "numel(shapes.bT)
|
| 668 |
}
|
| 669 |
]
|
| 670 |
},
|
|
@@ -756,7 +755,7 @@
|
|
| 756 |
"metrics": [
|
| 757 |
{
|
| 758 |
"type": "bandwidth",
|
| 759 |
-
"value": "numel(shapes.bT)
|
| 760 |
}
|
| 761 |
]
|
| 762 |
},
|
|
@@ -785,7 +784,7 @@
|
|
| 785 |
"metrics": [
|
| 786 |
{
|
| 787 |
"type": "bandwidth",
|
| 788 |
-
"value": "numel(shapes.bT)
|
| 789 |
}
|
| 790 |
]
|
| 791 |
},
|
|
@@ -813,11 +812,53 @@
|
|
| 813 |
"metrics": [
|
| 814 |
{
|
| 815 |
"type": "bandwidth",
|
| 816 |
-
"value": "numel(shapes.bT)
|
| 817 |
}
|
| 818 |
]
|
| 819 |
},
|
| 820 |
"attrs": { "K": 128, "N": 262144, "bits": 4, "block_size": 32 }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 821 |
}
|
| 822 |
]
|
| 823 |
}
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"tunableSpace": { "workgroupSize": [64, 128, 256] },
|
| 3 |
"cases": [
|
| 4 |
{
|
|
|
|
| 32 |
"metrics": [
|
| 33 |
{
|
| 34 |
"type": "bandwidth",
|
| 35 |
+
"value": "numel(shapes.bT) + numel(shapes.zeroPointsT) * 4 + (numel(shapes.aT) + numel(shapes.scalesT) + numel(shapes.biasT) + numel(shapes.yT)) * 4"
|
| 36 |
}
|
| 37 |
]
|
| 38 |
},
|
|
|
|
| 70 |
"metrics": [
|
| 71 |
{
|
| 72 |
"type": "bandwidth",
|
| 73 |
+
"value": "numel(shapes.bT) + numel(shapes.zeroPointsT) * 4 + (numel(shapes.aT) + numel(shapes.scalesT) + numel(shapes.biasT) + numel(shapes.yT)) * 4"
|
| 74 |
}
|
| 75 |
]
|
| 76 |
},
|
|
|
|
| 142 |
"metrics": [
|
| 143 |
{
|
| 144 |
"type": "bandwidth",
|
| 145 |
+
"value": "numel(shapes.bT) + (numel(shapes.aT) + numel(shapes.scalesT) + numel(shapes.yT)) * 4"
|
| 146 |
}
|
| 147 |
]
|
| 148 |
},
|
|
|
|
| 214 |
"metrics": [
|
| 215 |
{
|
| 216 |
"type": "bandwidth",
|
| 217 |
+
"value": "numel(shapes.bT) + (numel(shapes.aT) + numel(shapes.scalesT) + numel(shapes.yT)) * 4"
|
| 218 |
}
|
| 219 |
]
|
| 220 |
},
|
|
|
|
| 242 |
"metrics": [
|
| 243 |
{
|
| 244 |
"type": "bandwidth",
|
| 245 |
+
"value": "numel(shapes.bT) + (numel(shapes.aT) + numel(shapes.scalesT) + numel(shapes.yT)) * 4"
|
| 246 |
}
|
| 247 |
]
|
| 248 |
},
|
|
|
|
| 335 |
"metrics": [
|
| 336 |
{
|
| 337 |
"type": "bandwidth",
|
| 338 |
+
"value": "numel(shapes.bT) + numel(shapes.zeroPointsT) * 4 + (numel(shapes.aT) + numel(shapes.scalesT) + numel(shapes.biasT) + numel(shapes.yT)) * 4"
|
| 339 |
}
|
| 340 |
]
|
| 341 |
},
|
|
|
|
| 470 |
"metrics": [
|
| 471 |
{
|
| 472 |
"type": "bandwidth",
|
| 473 |
+
"value": "numel(shapes.bT) + (numel(shapes.aT) + numel(shapes.scalesT) + numel(shapes.yT)) * 2"
|
| 474 |
}
|
| 475 |
]
|
| 476 |
},
|
|
|
|
| 499 |
"metrics": [
|
| 500 |
{
|
| 501 |
"type": "bandwidth",
|
| 502 |
+
"value": "numel(shapes.bT) + (numel(shapes.aT) + numel(shapes.scalesT) + numel(shapes.yT)) * 2"
|
| 503 |
}
|
| 504 |
]
|
| 505 |
},
|
|
|
|
| 663 |
"metrics": [
|
| 664 |
{
|
| 665 |
"type": "bandwidth",
|
| 666 |
+
"value": "numel(shapes.bT) + numel(shapes.zeroPointsT) * 4 + (numel(shapes.aT) + numel(shapes.scalesT) + numel(shapes.yT)) * 4"
|
| 667 |
}
|
| 668 |
]
|
| 669 |
},
|
|
|
|
| 755 |
"metrics": [
|
| 756 |
{
|
| 757 |
"type": "bandwidth",
|
| 758 |
+
"value": "numel(shapes.bT) + (numel(shapes.aT) + numel(shapes.scalesT) + numel(shapes.yT)) * 4"
|
| 759 |
}
|
| 760 |
]
|
| 761 |
},
|
|
|
|
| 784 |
"metrics": [
|
| 785 |
{
|
| 786 |
"type": "bandwidth",
|
| 787 |
+
"value": "numel(shapes.bT) + (numel(shapes.aT) + numel(shapes.scalesT) + numel(shapes.yT)) * 4"
|
| 788 |
}
|
| 789 |
]
|
| 790 |
},
|
|
|
|
| 812 |
"metrics": [
|
| 813 |
{
|
| 814 |
"type": "bandwidth",
|
| 815 |
+
"value": "numel(shapes.bT) + (numel(shapes.aT) + numel(shapes.scalesT) + numel(shapes.yT)) * 4"
|
| 816 |
}
|
| 817 |
]
|
| 818 |
},
|
| 819 |
"attrs": { "K": 128, "N": 262144, "bits": 4, "block_size": 32 }
|
| 820 |
+
},
|
| 821 |
+
{
|
| 822 |
+
"name": "mmnb-q4g32-m47-k4096-n4096-one-below-register-tile-floor-pathology",
|
| 823 |
+
"preset": "model",
|
| 824 |
+
"vars": { "M": 47, "K": 4096, "N": 4096, "bits": 4, "blockSize": 32 },
|
| 825 |
+
"inputs": {
|
| 826 |
+
"aT": { "shape": [47, 4096], "dtype": "float32", "dist": "normal", "seed": 643, "scale": 0.2 },
|
| 827 |
+
"bT": { "shape": [4096, 128, 16], "dtype": "uint8", "dist": "q4pair", "seed": 644 },
|
| 828 |
+
"scalesT": {
|
| 829 |
+
"shape": [4096, 128],
|
| 830 |
+
"dtype": "float32",
|
| 831 |
+
"dist": "uniform",
|
| 832 |
+
"seed": 645,
|
| 833 |
+
"offset": 0.04,
|
| 834 |
+
"scale": 0.01,
|
| 835 |
+
"signed": false
|
| 836 |
+
}
|
| 837 |
+
},
|
| 838 |
+
"outputs": { "yT": { "shape": [47, 4096], "dtype": "float32" } },
|
| 839 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "2 * args.M * args.K * args.N" }] },
|
| 840 |
+
"attrs": { "K": 4096, "N": 4096, "bits": 4, "block_size": 32 }
|
| 841 |
+
},
|
| 842 |
+
{
|
| 843 |
+
"name": "mmnb-q4g32-m48-k4096-n4096-register-tile-floor-control",
|
| 844 |
+
"preset": "model",
|
| 845 |
+
"vars": { "M": 48, "K": 4096, "N": 4096, "bits": 4, "blockSize": 32 },
|
| 846 |
+
"inputs": {
|
| 847 |
+
"aT": { "shape": [48, 4096], "dtype": "float32", "dist": "normal", "seed": 646, "scale": 0.2 },
|
| 848 |
+
"bT": { "shape": [4096, 128, 16], "dtype": "uint8", "dist": "q4pair", "seed": 647 },
|
| 849 |
+
"scalesT": {
|
| 850 |
+
"shape": [4096, 128],
|
| 851 |
+
"dtype": "float32",
|
| 852 |
+
"dist": "uniform",
|
| 853 |
+
"seed": 648,
|
| 854 |
+
"offset": 0.04,
|
| 855 |
+
"scale": 0.01,
|
| 856 |
+
"signed": false
|
| 857 |
+
}
|
| 858 |
+
},
|
| 859 |
+
"outputs": { "yT": { "shape": [48, 4096], "dtype": "float32" } },
|
| 860 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "2 * args.M * args.K * args.N" }] },
|
| 861 |
+
"attrs": { "K": 4096, "N": 4096, "bits": 4, "block_size": 32 }
|
| 862 |
}
|
| 863 |
]
|
| 864 |
}
|
build/webgpu/manifest.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/webgpu/matmul-nbits-dp4a-quantize.wgsl.jinja
CHANGED
|
@@ -13,11 +13,10 @@ var<workgroup> maxAbs: array<f32, WG>;
|
|
| 13 |
@compute @workgroup_size(WG)
|
| 14 |
fn main(
|
| 15 |
@builtin(workgroup_id) wid: vec3<u32>,
|
| 16 |
-
@builtin(num_workgroups) nwg: vec3<u32>,
|
| 17 |
@builtin(local_invocation_id) lid: vec3<u32>
|
| 18 |
) {
|
| 19 |
// 2D-folded dispatch: wid.y carries the high bits past the per-dimension limit.
|
| 20 |
-
let wgFlat = wid.x + wid.y *
|
| 21 |
let i = wgFlat * WG + lid.x;
|
| 22 |
var v = vec4<f32>(0.0);
|
| 23 |
if (i < VEC4_COUNT) {
|
|
|
|
| 13 |
@compute @workgroup_size(WG)
|
| 14 |
fn main(
|
| 15 |
@builtin(workgroup_id) wid: vec3<u32>,
|
|
|
|
| 16 |
@builtin(local_invocation_id) lid: vec3<u32>
|
| 17 |
) {
|
| 18 |
// 2D-folded dispatch: wid.y carries the high bits past the per-dimension limit.
|
| 19 |
+
let wgFlat = wid.x + wid.y * {{ DISPATCH_FOLD_WIDTH }}u;
|
| 20 |
let i = wgFlat * WG + lid.x;
|
| 21 |
var v = vec4<f32>(0.0);
|
| 22 |
if (i < VEC4_COUNT) {
|
build/webgpu/matmul-nbits-gemv-q4.wgsl.jinja
CHANGED
|
@@ -1,35 +1,43 @@
|
|
| 1 |
-
{% if
|
| 2 |
-
enable
|
| 3 |
{% endif %}
|
| 4 |
{{ env.wgsl.resourceDeclarations }}
|
| 5 |
-
//
|
| 6 |
-
//
|
| 7 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
{% set nCols = gemvNCols %}
|
| 9 |
{% set groups = 2 if nCols == 8 else 1 %}
|
| 10 |
{% set comps = ["x", "y", "z", "w"] %}
|
| 11 |
-
{% set codesPerByte = 4 if bits == 2 else (2 if bits == 4 else 1) %}
|
| 12 |
-
{% set codeMask = 3 if bits == 2 else (15 if bits == 4 else 255) %}
|
| 13 |
|
| 14 |
const WG: u32 = {{ workgroupSize }}u;
|
| 15 |
const N_COLS: u32 = {{ nCols }}u;
|
| 16 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
{% for g in range(groups) %}
|
| 19 |
{% set sfx = "" if groups == 1 else g %}
|
| 20 |
var<workgroup> partials{{ sfx }}: array<vec4<f32>, {{ workgroupSize }}>;
|
| 21 |
{% endfor %}
|
| 22 |
|
| 23 |
-
// {{ bits }}-bit unpack: q2 packs four codes per byte, q4 two nibbles, q8 one byte/weight.
|
| 24 |
-
// The whole stored byte, addressed by its index within the block rather than by
|
| 25 |
-
// one of the codes inside it.
|
| 26 |
-
fn packed_byte(n: u32, block: u32, byte_index: u32) -> u32 {
|
| 27 |
-
return b[(n * params.kBlocks + block) * params.blobSize + byte_index];
|
| 28 |
-
}
|
| 29 |
-
|
| 30 |
// Per-(column, block) zero point. Symmetric weights use the baked default
|
| 31 |
-
// ({{ defaultZero }}); asymmetric weights carry one widened
|
| 32 |
-
// in row-major [N, kBlocks] layout.
|
| 33 |
fn zero_point({% if hasZero %}n: u32, block: u32{% endif %}) -> f32 {
|
| 34 |
{% if hasZero %}
|
| 35 |
return f32(zero_points[n * params.kBlocks + block]);
|
|
@@ -38,17 +46,27 @@ fn zero_point({% if hasZero %}n: u32, block: u32{% endif %}) -> f32 {
|
|
| 38 |
{% endif %}
|
| 39 |
}
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
@compute @workgroup_size({{ workgroupSize }}, 1, 1)
|
| 42 |
fn main(
|
| 43 |
@builtin(workgroup_id) wid: vec3<u32>,
|
| 44 |
-
@builtin(
|
| 45 |
-
@builtin(
|
|
|
|
| 46 |
) {
|
| 47 |
let tid = lid.x;
|
| 48 |
// 2D-folded dispatch: wid.y carries the high bits past the per-dimension
|
| 49 |
// limit (vocab-sized N). The fold over-dispatches, so the tail workgroups
|
| 50 |
// return before any barrier.
|
| 51 |
-
let col_base = (wid.x + wid.y *
|
| 52 |
if (col_base >= params.N) {
|
| 53 |
return;
|
| 54 |
}
|
|
@@ -57,47 +75,74 @@ fn main(
|
|
| 57 |
var acc{{ sfx }} = vec4<f32>(0.0);
|
| 58 |
{% endfor %}
|
| 59 |
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
let
|
| 66 |
-
{%
|
| 67 |
-
{%
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
| 69 |
{% else %}
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
{
|
| 73 |
{% endfor %}
|
|
|
|
|
|
|
| 74 |
{% for g in range(groups) %}
|
| 75 |
{% set sfx = "" if groups == 1 else g %}
|
| 76 |
{% for c in range(4) %}
|
| 77 |
{% set i = g * 4 + c %}
|
| 78 |
{% if i == 0 %}
|
| 79 |
-
|
| 80 |
-
let zero0 = zero_point({% if hasZero %}col_base + 0u, block{% endif %});
|
| 81 |
-
let word0 = packed_byte(col_base + 0u, block, byte_index);
|
| 82 |
-
{% for j in range(codesPerByte) %}
|
| 83 |
-
acc{{ sfx }}.x = acc{{ sfx }}.x
|
| 84 |
-
+ av{{ j }} * (f32((word0 >> {{ j * bits }}u) & {{ codeMask }}u) - zero0) * scale0;
|
| 85 |
-
{% endfor %}
|
| 86 |
{% else %}
|
| 87 |
if (col_base + {{ i }}u < params.N) {
|
| 88 |
-
let scale{{ i }} = f32(scales[(col_base + {{ i }}u) * params.kBlocks + block]);
|
| 89 |
-
let zero{{ i }} = zero_point({% if hasZero %}col_base + {{ i }}u, block{% endif %});
|
| 90 |
-
let word{{ i }} = packed_byte(col_base + {{ i }}u, block, byte_index);
|
| 91 |
-
{% for j in range(codesPerByte) %}
|
| 92 |
-
acc{{ sfx }}.{{ comps[c] }} = acc{{ sfx }}.{{ comps[c] }}
|
| 93 |
-
+ av{{ j }} * (f32((word{{ i }} >> {{ j * bits }}u) & {{ codeMask }}u) - zero{{ i }}) * scale{{ i }};
|
| 94 |
-
{% endfor %}
|
| 95 |
-
}
|
| 96 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
{% endfor %}
|
| 98 |
{% endfor %}
|
| 99 |
}
|
| 100 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
{% for g in range(groups) %}
|
| 102 |
{% set sfx = "" if groups == 1 else g %}
|
| 103 |
partials{{ sfx }}[tid] = acc{{ sfx }};
|
|
@@ -124,6 +169,7 @@ fn main(
|
|
| 124 |
{% set sfx = "" if groups == 1 else g %}
|
| 125 |
let total{{ sfx }} = partials{{ sfx }}[0];
|
| 126 |
{% endfor %}
|
|
|
|
| 127 |
{% for g in range(groups) %}
|
| 128 |
{% set sfx = "" if groups == 1 else g %}
|
| 129 |
{% for c in range(4) %}
|
|
|
|
| 1 |
+
{% if useSubgroups %}
|
| 2 |
+
enable subgroups;
|
| 3 |
{% endif %}
|
| 4 |
{{ env.wgsl.resourceDeclarations }}
|
| 5 |
+
// com.microsoft.MatMulNBits decode GEMV: one activation row against the packed
|
| 6 |
+
// weight blob. A workgroup owns gemvNCols output columns; its lanes stride the
|
| 7 |
+
// reduction axis in whole VECTORS of the blob -- a vec4<u32> of sixteen packed
|
| 8 |
+
// bytes when the quantization block is a multiple of sixteen bytes, else one
|
| 9 |
+
// u32 word -- so every weight byte arrives in the widest load the layout admits.
|
| 10 |
+
// A vector never straddles a block, so one scale (and zero point) covers every
|
| 11 |
+
// code it carries.
|
| 12 |
+
//
|
| 13 |
+
// Per vector and column the lane accumulates dot(a, codes) over the vector's
|
| 14 |
+
// codes and applies the block's (scale, zero) once:
|
| 15 |
+
// sum_j a_j (c_j - z) s == s (dot(a, c) - z sum_j a_j)
|
| 16 |
+
// so the per-code work is a shift, a mask, a convert and one fma, and the
|
| 17 |
+
// activation sum is shared by every column. Partials fold through workgroup
|
| 18 |
+
// memory, so no subgroup feature is required.
|
| 19 |
{% set nCols = gemvNCols %}
|
| 20 |
{% set groups = 2 if nCols == 8 else 1 %}
|
| 21 |
{% set comps = ["x", "y", "z", "w"] %}
|
|
|
|
|
|
|
| 22 |
|
| 23 |
const WG: u32 = {{ workgroupSize }}u;
|
| 24 |
const N_COLS: u32 = {{ nCols }}u;
|
| 25 |
+
const VEC_PER_COL: u32 = {{ vecPerCol }}u;
|
| 26 |
+
const VEC_PER_BLOCK: u32 = {{ vecPerBlock }}u;
|
| 27 |
+
{% if actVec4 %}
|
| 28 |
+
const VEC4_PER_VEC: u32 = {{ codesPerVec4 }}u;
|
| 29 |
+
{% else %}
|
| 30 |
+
const CODES_PER_VEC: u32 = {{ codesPerVec }}u;
|
| 31 |
+
{% endif %}
|
| 32 |
|
| 33 |
{% for g in range(groups) %}
|
| 34 |
{% set sfx = "" if groups == 1 else g %}
|
| 35 |
var<workgroup> partials{{ sfx }}: array<vec4<f32>, {{ workgroupSize }}>;
|
| 36 |
{% endfor %}
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
// Per-(column, block) zero point. Symmetric weights use the baked default
|
| 39 |
+
// ({{ defaultZero }}); asymmetric weights carry one widened value per (col,
|
| 40 |
+
// block) in row-major [N, kBlocks] layout.
|
| 41 |
fn zero_point({% if hasZero %}n: u32, block: u32{% endif %}) -> f32 {
|
| 42 |
{% if hasZero %}
|
| 43 |
return f32(zero_points[n * params.kBlocks + block]);
|
|
|
|
| 46 |
{% endif %}
|
| 47 |
}
|
| 48 |
|
| 49 |
+
{% macro vec_dot(words) %}
|
| 50 |
+
{% for w in range(vecWords) %}
|
| 51 |
+
{% set word = (words ~ "." ~ comps[w]) if vecWords == 4 else words %}
|
| 52 |
+
{% for h in range(codesPerWord) %}
|
| 53 |
+
dot = dot + a{{ w * codesPerWord + h }} * f32(({{ word }} >> {{ h * bits }}u) & {{ codeMask }}u);
|
| 54 |
+
{% endfor %}
|
| 55 |
+
{% endfor %}
|
| 56 |
+
{%- endmacro %}
|
| 57 |
+
|
| 58 |
@compute @workgroup_size({{ workgroupSize }}, 1, 1)
|
| 59 |
fn main(
|
| 60 |
@builtin(workgroup_id) wid: vec3<u32>,
|
| 61 |
+
@builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
|
| 62 |
+
@builtin(subgroup_invocation_id) sgLane: u32,
|
| 63 |
+
@builtin(subgroup_size) sgSize: u32{% endif %}
|
| 64 |
) {
|
| 65 |
let tid = lid.x;
|
| 66 |
// 2D-folded dispatch: wid.y carries the high bits past the per-dimension
|
| 67 |
// limit (vocab-sized N). The fold over-dispatches, so the tail workgroups
|
| 68 |
// return before any barrier.
|
| 69 |
+
let col_base = (wid.x + wid.y * {{ DISPATCH_FOLD_WIDTH }}u) * N_COLS;
|
| 70 |
if (col_base >= params.N) {
|
| 71 |
return;
|
| 72 |
}
|
|
|
|
| 75 |
var acc{{ sfx }} = vec4<f32>(0.0);
|
| 76 |
{% endfor %}
|
| 77 |
|
| 78 |
+
for (var v = tid; v < VEC_PER_COL; v = v + WG) {
|
| 79 |
+
let block = v / VEC_PER_BLOCK;
|
| 80 |
+
{% if actVec4 %}
|
| 81 |
+
let abase = v * VEC4_PER_VEC;
|
| 82 |
+
{% for q in range(codesPerVec4) %}
|
| 83 |
+
let av{{ q }} = vec4<f32>(a[abase + {{ q }}u]);
|
| 84 |
+
{% endfor %}
|
| 85 |
+
{% for q in range(codesPerVec4) %}
|
| 86 |
+
{% for c in range(4) %}
|
| 87 |
+
let a{{ q * 4 + c }} = av{{ q }}.{{ comps[c] }};
|
| 88 |
+
{% endfor %}
|
| 89 |
+
{% endfor %}
|
| 90 |
{% else %}
|
| 91 |
+
let k0 = v * CODES_PER_VEC;
|
| 92 |
+
{% for j in range(codesPerVec) %}
|
| 93 |
+
let a{{ j }} = select(0.0, f32(a[min(k0 + {{ j }}u, params.K - 1u)]), k0 + {{ j }}u < params.K);
|
| 94 |
{% endfor %}
|
| 95 |
+
{% endif %}
|
| 96 |
+
let asum = {% for j in range(codesPerVec) %}{{ " + " if j > 0 else "" }}a{{ j }}{% endfor %};
|
| 97 |
{% for g in range(groups) %}
|
| 98 |
{% set sfx = "" if groups == 1 else g %}
|
| 99 |
{% for c in range(4) %}
|
| 100 |
{% set i = g * 4 + c %}
|
| 101 |
{% if i == 0 %}
|
| 102 |
+
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
{% else %}
|
| 104 |
if (col_base + {{ i }}u < params.N) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
{% endif %}
|
| 106 |
+
let n = col_base + {{ i }}u;
|
| 107 |
+
let words = b[n * VEC_PER_COL + v];
|
| 108 |
+
let scale = f32(scales[n * params.kBlocks + block]);
|
| 109 |
+
let zero = zero_point({% if hasZero %}n, block{% endif %});
|
| 110 |
+
var dot = 0.0;
|
| 111 |
+
{{ vec_dot("words") }}
|
| 112 |
+
acc{{ sfx }}.{{ comps[c] }} = acc{{ sfx }}.{{ comps[c] }} + (dot - zero * asum) * scale;
|
| 113 |
+
}
|
| 114 |
{% endfor %}
|
| 115 |
{% endfor %}
|
| 116 |
}
|
| 117 |
|
| 118 |
+
{% if useSubgroups %}
|
| 119 |
+
// Subgroup fold: one collective per accumulator, then the WG / subgroup-size
|
| 120 |
+
// per-subgroup partials fold once through workgroup memory. The lanes of a
|
| 121 |
+
// subgroup are contiguous in local_invocation_id, so tid / sgSize names it.
|
| 122 |
+
// Every lane reaches this point (the only early return is workgroup-uniform),
|
| 123 |
+
// so the collective runs in uniform control flow.
|
| 124 |
+
{% for g in range(groups) %}
|
| 125 |
+
{% set sfx = "" if groups == 1 else g %}
|
| 126 |
+
let sgSum{{ sfx }} = subgroupAdd(acc{{ sfx }});
|
| 127 |
+
{% endfor %}
|
| 128 |
+
if (sgLane == 0u) {
|
| 129 |
+
{% for g in range(groups) %}
|
| 130 |
+
{% set sfx = "" if groups == 1 else g %}
|
| 131 |
+
partials{{ sfx }}[tid / sgSize] = sgSum{{ sfx }};
|
| 132 |
+
{% endfor %}
|
| 133 |
+
}
|
| 134 |
+
workgroupBarrier();
|
| 135 |
+
|
| 136 |
+
if (tid == 0u) {
|
| 137 |
+
let subgroupCount = WG / sgSize;
|
| 138 |
+
{% for g in range(groups) %}
|
| 139 |
+
{% set sfx = "" if groups == 1 else g %}
|
| 140 |
+
var total{{ sfx }} = partials{{ sfx }}[0];
|
| 141 |
+
for (var i = 1u; i < subgroupCount; i = i + 1u) {
|
| 142 |
+
total{{ sfx }} = total{{ sfx }} + partials{{ sfx }}[i];
|
| 143 |
+
}
|
| 144 |
+
{% endfor %}
|
| 145 |
+
{% else %}
|
| 146 |
{% for g in range(groups) %}
|
| 147 |
{% set sfx = "" if groups == 1 else g %}
|
| 148 |
partials{{ sfx }}[tid] = acc{{ sfx }};
|
|
|
|
| 169 |
{% set sfx = "" if groups == 1 else g %}
|
| 170 |
let total{{ sfx }} = partials{{ sfx }}[0];
|
| 171 |
{% endfor %}
|
| 172 |
+
{% endif %}
|
| 173 |
{% for g in range(groups) %}
|
| 174 |
{% set sfx = "" if groups == 1 else g %}
|
| 175 |
{% for c in range(4) %}
|
build/webgpu/matmul-nbits-q4-dp4a-prefill.wgsl.jinja
CHANGED
|
@@ -53,11 +53,14 @@ fn main(
|
|
| 53 |
tA[r][w] = select(0u, a_quant[am * K4 + kt * 8u + w], am < M);
|
| 54 |
let bn = nBase + r;
|
| 55 |
if (bn < N) {
|
| 56 |
-
// Two blob bytes hold this word's four sequential weights
|
| 57 |
-
//
|
|
|
|
|
|
|
| 58 |
let byteBase = (bn * KBLOCKS + block) * BLOB_SIZE + ((kBase % BLOCK_SIZE) + w * 4u) / 2u;
|
| 59 |
-
let
|
| 60 |
-
let
|
|
|
|
| 61 |
let quads = vec4<i32>(i32(b0 & 15u), i32((b0 >> 4u) & 15u), i32(b1 & 15u), i32((b1 >> 4u) & 15u));
|
| 62 |
tB[r][w] = pack4xI8(quads - vec4<i32>(8));
|
| 63 |
} else {
|
|
|
|
| 53 |
tA[r][w] = select(0u, a_quant[am * K4 + kt * 8u + w], am < M);
|
| 54 |
let bn = nBase + r;
|
| 55 |
if (bn < N) {
|
| 56 |
+
// Two blob bytes hold this word's four sequential weights (low nibble =
|
| 57 |
+
// even k). The blob is packed four bytes per u32 word, and an even byte
|
| 58 |
+
// index inside a 4-byte-aligned blob never straddles a word, so both
|
| 59 |
+
// bytes come from one load. Rebias by the implicit zero point 8.
|
| 60 |
let byteBase = (bn * KBLOCKS + block) * BLOB_SIZE + ((kBase % BLOCK_SIZE) + w * 4u) / 2u;
|
| 61 |
+
let pair = b[byteBase >> 2u] >> ((byteBase & 3u) * 8u);
|
| 62 |
+
let b0 = pair & 255u;
|
| 63 |
+
let b1 = (pair >> 8u) & 255u;
|
| 64 |
let quads = vec4<i32>(i32(b0 & 15u), i32((b0 >> 4u) & 15u), i32(b1 & 15u), i32((b1 >> 4u) & 15u));
|
| 65 |
tB[r][w] = pack4xI8(quads - vec4<i32>(8));
|
| 66 |
} else {
|
build/webgpu/matmul-nbits-q4-prefill-tile4x4.wgsl.jinja
CHANGED
|
@@ -1,38 +1,28 @@
|
|
| 1 |
-
{% if usesF16 %}
|
| 2 |
-
enable f16;
|
| 3 |
-
{% endif %}
|
| 4 |
{{ env.wgsl.resourceDeclarations }}
|
| 5 |
-
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
const TILE_M: u32 = 4u;
|
| 8 |
const TILE_N: u32 = 4u;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
var<workgroup> partial0: array<vec4<f32>, {{ workgroupSize }}>;
|
| 11 |
var<workgroup> partial1: array<vec4<f32>, {{ workgroupSize }}>;
|
| 12 |
var<workgroup> partial2: array<vec4<f32>, {{ workgroupSize }}>;
|
| 13 |
var<workgroup> partial3: array<vec4<f32>, {{ workgroupSize }}>;
|
| 14 |
|
| 15 |
-
// {{ bits }}-bit unpack: q2 stores four codes per byte, q4 two, and q8 one.
|
| 16 |
-
{% macro matmul_nbits_packed_code(fn="packed_weight", buffer="b", kBlocks="params.kBlocks", blobSize="params.blobSize", bits=4) %}
|
| 17 |
-
fn {{ fn }}(n: u32, block: u32, offset: u32) -> u32 {
|
| 18 |
-
{% if bits == 2 %}
|
| 19 |
-
let byte_index = offset / 4u;
|
| 20 |
-
let shift = (offset % 4u) * 2u;
|
| 21 |
-
let packed_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + byte_index;
|
| 22 |
-
return ({{ buffer }}[packed_index] >> shift) & 3u;
|
| 23 |
-
{% elif bits == 4 %}
|
| 24 |
-
let byte_index = offset / 2u;
|
| 25 |
-
let shift = (offset % 2u) * 4u;
|
| 26 |
-
let packed_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + byte_index;
|
| 27 |
-
return ({{ buffer }}[packed_index] >> shift) & 15u;
|
| 28 |
-
{% else %}
|
| 29 |
-
let packed_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + offset;
|
| 30 |
-
return {{ buffer }}[packed_index] & 255u;
|
| 31 |
-
{% endif %}
|
| 32 |
-
}
|
| 33 |
-
{%- endmacro %}
|
| 34 |
-
{{- matmul_nbits_packed_code(bits=bits) }}
|
| 35 |
-
|
| 36 |
fn store_row(row: u32, col_base: u32, value: vec4<f32>) {
|
| 37 |
if (row >= params.rows) {
|
| 38 |
return;
|
|
@@ -54,78 +44,103 @@ fn main(
|
|
| 54 |
@builtin(num_workgroups) nwg: vec3<u32>
|
| 55 |
) {
|
| 56 |
let tid = lid.x;
|
|
|
|
|
|
|
| 57 |
// Column tiles fold into z past the per-dimension limit (vocab-sized N,
|
| 58 |
// where y already carries the row-tile grid-stride). Over-dispatched tail
|
| 59 |
-
// workgroups return before any barrier
|
| 60 |
-
|
| 61 |
-
|
|
|
|
|
|
|
| 62 |
return;
|
| 63 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
// Grid-stride over row tiles because dispatch y is device-clamped. This loop
|
| 66 |
// reduces to one iteration when the dispatch covers every tile directly.
|
| 67 |
for (var row_tile = wid.y; row_tile * TILE_M < params.rows; row_tile = row_tile + nwg.y) {
|
| 68 |
let row_base = row_tile * TILE_M;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
var acc0 = vec4<f32>(0.0);
|
| 71 |
var acc1 = vec4<f32>(0.0);
|
| 72 |
var acc2 = vec4<f32>(0.0);
|
| 73 |
var acc3 = vec4<f32>(0.0);
|
| 74 |
|
| 75 |
-
for (var
|
| 76 |
-
let
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
| 86 |
{% if hasZero %}
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
f32(
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
let zero = vec4<f32>({{ defaultZero }});
|
| 95 |
{% endif %}
|
| 96 |
-
let w = (vec4<f32>(
|
| 97 |
-
f32(packed_weight(col_base + 0u, block, offset)),
|
| 98 |
-
f32(packed_weight(col_base + 1u, block, offset)),
|
| 99 |
-
f32(packed_weight(col_base + 2u, block, offset)),
|
| 100 |
-
f32(packed_weight(col_base + 3u, block, offset))
|
| 101 |
-
) - zero) * scale;
|
| 102 |
-
|
| 103 |
-
var a0 = 0.0;
|
| 104 |
-
var a1 = 0.0;
|
| 105 |
-
var a2 = 0.0;
|
| 106 |
-
var a3 = 0.0;
|
| 107 |
-
if (row_base + 0u < params.rows) {
|
| 108 |
-
a0 = f32(a[(row_base + 0u) * params.K + k]);
|
| 109 |
-
}
|
| 110 |
-
if (row_base + 1u < params.rows) {
|
| 111 |
-
a1 = f32(a[(row_base + 1u) * params.K + k]);
|
| 112 |
}
|
| 113 |
-
if (
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
}
|
| 116 |
-
if (
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
}
|
| 119 |
-
|
| 120 |
-
acc0 = acc0 + a0 * w;
|
| 121 |
-
acc1 = acc1 + a1 * w;
|
| 122 |
-
acc2 = acc2 + a2 * w;
|
| 123 |
-
acc3 = acc3 + a3 * w;
|
| 124 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
}
|
| 126 |
|
| 127 |
// Sync before reusing the shared partial buffers: on a subsequent grid-stride
|
| 128 |
-
// iteration the previous iteration's
|
| 129 |
workgroupBarrier();
|
| 130 |
partial0[tid] = acc0;
|
| 131 |
partial1[tid] = acc1;
|
|
@@ -133,38 +148,35 @@ fn main(
|
|
| 133 |
partial3[tid] = acc3;
|
| 134 |
workgroupBarrier();
|
| 135 |
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
}
|
| 141 |
-
if (tid < stride) {
|
| 142 |
partial0[tid] = partial0[tid] + partial0[tid + stride];
|
| 143 |
partial1[tid] = partial1[tid] + partial1[tid + stride];
|
| 144 |
partial2[tid] = partial2[tid] + partial2[tid + stride];
|
| 145 |
partial3[tid] = partial3[tid] + partial3[tid + stride];
|
| 146 |
}
|
| 147 |
-
stride = stride / 2u;
|
| 148 |
workgroupBarrier();
|
| 149 |
}
|
| 150 |
|
| 151 |
-
if (
|
| 152 |
{% if hasBias %}
|
| 153 |
let biasv = vec4<f32>(
|
| 154 |
f32(bias[col_base + 0u]),
|
| 155 |
-
f32(bias[col_base + 1u]),
|
| 156 |
-
f32(bias[col_base + 2u]),
|
| 157 |
-
f32(bias[col_base + 3u])
|
| 158 |
);
|
| 159 |
-
store_row(row_base + 0u, col_base, partial0[
|
| 160 |
-
store_row(row_base + 1u, col_base, partial1[
|
| 161 |
-
store_row(row_base + 2u, col_base, partial2[
|
| 162 |
-
store_row(row_base + 3u, col_base, partial3[
|
| 163 |
{% else %}
|
| 164 |
-
store_row(row_base + 0u, col_base, partial0[
|
| 165 |
-
store_row(row_base + 1u, col_base, partial1[
|
| 166 |
-
store_row(row_base + 2u, col_base, partial2[
|
| 167 |
-
store_row(row_base + 3u, col_base, partial3[
|
| 168 |
{% endif %}
|
| 169 |
}
|
| 170 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
{{ env.wgsl.resourceDeclarations }}
|
| 2 |
+
// com.microsoft.MatMulNBits small-M prefill over the packed blob: any M >= 2, K
|
| 3 |
+
// a whole number of quantization blocks. A workgroup covers TILE_M = 4
|
| 4 |
+
// activation rows by TILE_N * COL_GROUPS output columns. Its lanes split into
|
| 5 |
+
// COL_GROUPS groups of K_LANES; a group owns four adjacent columns and its
|
| 6 |
+
// lanes advance through the blob one packed u32 word (32 / bits codes) at a
|
| 7 |
+
// time. Each lane retains the four columns' words while applying them to four
|
| 8 |
+
// activation rows, and one (scale, zero) pair per column serves every code in
|
| 9 |
+
// the word. A word never straddles a quantization block.
|
| 10 |
+
// Partials fold inside each column group through workgroup memory (portable:
|
| 11 |
+
// no subgroup feature). K_LANES shrinks with the words per column for short K.
|
| 12 |
const TILE_M: u32 = 4u;
|
| 13 |
const TILE_N: u32 = 4u;
|
| 14 |
+
const K_LANES: u32 = {{ kLanes }}u;
|
| 15 |
+
const COL_GROUPS: u32 = {{ colGroups }}u;
|
| 16 |
+
const COLS_PER_WORKGROUP: u32 = TILE_N * COL_GROUPS;
|
| 17 |
+
const WORDS_PER_COL: u32 = {{ wordsPerCol }}u;
|
| 18 |
+
const WORDS_PER_BLOCK: u32 = {{ wordsPerBlock }}u;
|
| 19 |
+
const CODES_PER_WORD: u32 = {{ codesPerWord }}u;
|
| 20 |
|
| 21 |
var<workgroup> partial0: array<vec4<f32>, {{ workgroupSize }}>;
|
| 22 |
var<workgroup> partial1: array<vec4<f32>, {{ workgroupSize }}>;
|
| 23 |
var<workgroup> partial2: array<vec4<f32>, {{ workgroupSize }}>;
|
| 24 |
var<workgroup> partial3: array<vec4<f32>, {{ workgroupSize }}>;
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
fn store_row(row: u32, col_base: u32, value: vec4<f32>) {
|
| 27 |
if (row >= params.rows) {
|
| 28 |
return;
|
|
|
|
| 44 |
@builtin(num_workgroups) nwg: vec3<u32>
|
| 45 |
) {
|
| 46 |
let tid = lid.x;
|
| 47 |
+
let group = tid / K_LANES;
|
| 48 |
+
let kl = tid % K_LANES;
|
| 49 |
// Column tiles fold into z past the per-dimension limit (vocab-sized N,
|
| 50 |
// where y already carries the row-tile grid-stride). Over-dispatched tail
|
| 51 |
+
// workgroups return before any barrier; a column GROUP past N inside a live
|
| 52 |
+
// workgroup keeps running on zeroed loads so the barriers stay uniform, and
|
| 53 |
+
// its store is skipped.
|
| 54 |
+
let workgroup_col = (wid.x + wid.z * {{ DISPATCH_FOLD_WIDTH }}u) * COLS_PER_WORKGROUP;
|
| 55 |
+
if (workgroup_col >= params.N) {
|
| 56 |
return;
|
| 57 |
}
|
| 58 |
+
let col_base = workgroup_col + group * TILE_N;
|
| 59 |
+
let group_valid = col_base < params.N;
|
| 60 |
+
let col1 = col_base + 1u < params.N;
|
| 61 |
+
let col2 = col_base + 2u < params.N;
|
| 62 |
+
let col3 = col_base + 3u < params.N;
|
| 63 |
|
| 64 |
// Grid-stride over row tiles because dispatch y is device-clamped. This loop
|
| 65 |
// reduces to one iteration when the dispatch covers every tile directly.
|
| 66 |
for (var row_tile = wid.y; row_tile * TILE_M < params.rows; row_tile = row_tile + nwg.y) {
|
| 67 |
let row_base = row_tile * TILE_M;
|
| 68 |
+
// Rows past the end clamp onto the last real row; their accumulators are
|
| 69 |
+
// finite and the store guard drops them.
|
| 70 |
+
let row1 = row_base + 1u < params.rows;
|
| 71 |
+
let row2 = row_base + 2u < params.rows;
|
| 72 |
+
let row3 = row_base + 3u < params.rows;
|
| 73 |
+
let abase0 = row_base * params.K;
|
| 74 |
+
let abase1 = min(row_base + 1u, params.rows - 1u) * params.K;
|
| 75 |
+
let abase2 = min(row_base + 2u, params.rows - 1u) * params.K;
|
| 76 |
+
let abase3 = min(row_base + 3u, params.rows - 1u) * params.K;
|
| 77 |
|
| 78 |
var acc0 = vec4<f32>(0.0);
|
| 79 |
var acc1 = vec4<f32>(0.0);
|
| 80 |
var acc2 = vec4<f32>(0.0);
|
| 81 |
var acc3 = vec4<f32>(0.0);
|
| 82 |
|
| 83 |
+
for (var w = kl; w < WORDS_PER_COL; w = w + K_LANES) {
|
| 84 |
+
let block = w / WORDS_PER_BLOCK;
|
| 85 |
+
let k0 = w * CODES_PER_WORD;
|
| 86 |
+
// One packed word per column, plus its block's scale and zero point. A
|
| 87 |
+
// column past N reads nothing and dequantizes to zero (scale 0).
|
| 88 |
+
var q0 = 0u;
|
| 89 |
+
var q1 = 0u;
|
| 90 |
+
var q2 = 0u;
|
| 91 |
+
var q3 = 0u;
|
| 92 |
+
var scale = vec4<f32>(0.0);
|
| 93 |
+
var zero = vec4<f32>({{ defaultZero }});
|
| 94 |
+
if (group_valid) {
|
| 95 |
+
q0 = b[(col_base + 0u) * WORDS_PER_COL + w];
|
| 96 |
+
scale.x = f32(scales[(col_base + 0u) * params.kBlocks + block]);
|
| 97 |
{% if hasZero %}
|
| 98 |
+
zero.x = f32(zero_points[(col_base + 0u) * params.kBlocks + block]);
|
| 99 |
+
{% endif %}
|
| 100 |
+
if (col1) {
|
| 101 |
+
q1 = b[(col_base + 1u) * WORDS_PER_COL + w];
|
| 102 |
+
scale.y = f32(scales[(col_base + 1u) * params.kBlocks + block]);
|
| 103 |
+
{% if hasZero %}
|
| 104 |
+
zero.y = f32(zero_points[(col_base + 1u) * params.kBlocks + block]);
|
|
|
|
| 105 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
}
|
| 107 |
+
if (col2) {
|
| 108 |
+
q2 = b[(col_base + 2u) * WORDS_PER_COL + w];
|
| 109 |
+
scale.z = f32(scales[(col_base + 2u) * params.kBlocks + block]);
|
| 110 |
+
{% if hasZero %}
|
| 111 |
+
zero.z = f32(zero_points[(col_base + 2u) * params.kBlocks + block]);
|
| 112 |
+
{% endif %}
|
| 113 |
}
|
| 114 |
+
if (col3) {
|
| 115 |
+
q3 = b[(col_base + 3u) * WORDS_PER_COL + w];
|
| 116 |
+
scale.w = f32(scales[(col_base + 3u) * params.kBlocks + block]);
|
| 117 |
+
{% if hasZero %}
|
| 118 |
+
zero.w = f32(zero_points[(col_base + 3u) * params.kBlocks + block]);
|
| 119 |
+
{% endif %}
|
| 120 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
}
|
| 122 |
+
{% for j in range(codesPerWord) %}
|
| 123 |
+
{
|
| 124 |
+
let wt = (vec4<f32>(
|
| 125 |
+
f32((q0 >> {{ j * bits }}u) & {{ codeMask }}u),
|
| 126 |
+
f32((q1 >> {{ j * bits }}u) & {{ codeMask }}u),
|
| 127 |
+
f32((q2 >> {{ j * bits }}u) & {{ codeMask }}u),
|
| 128 |
+
f32((q3 >> {{ j * bits }}u) & {{ codeMask }}u)) - zero) * scale;
|
| 129 |
+
let k = k0 + {{ j }}u;
|
| 130 |
+
let a0 = f32(a[abase0 + k]);
|
| 131 |
+
let a1 = select(0.0, f32(a[abase1 + k]), row1);
|
| 132 |
+
let a2 = select(0.0, f32(a[abase2 + k]), row2);
|
| 133 |
+
let a3 = select(0.0, f32(a[abase3 + k]), row3);
|
| 134 |
+
acc0 = acc0 + a0 * wt;
|
| 135 |
+
acc1 = acc1 + a1 * wt;
|
| 136 |
+
acc2 = acc2 + a2 * wt;
|
| 137 |
+
acc3 = acc3 + a3 * wt;
|
| 138 |
+
}
|
| 139 |
+
{% endfor %}
|
| 140 |
}
|
| 141 |
|
| 142 |
// Sync before reusing the shared partial buffers: on a subsequent grid-stride
|
| 143 |
+
// iteration the previous iteration's group-leader store may still be reading.
|
| 144 |
workgroupBarrier();
|
| 145 |
partial0[tid] = acc0;
|
| 146 |
partial1[tid] = acc1;
|
|
|
|
| 148 |
partial3[tid] = acc3;
|
| 149 |
workgroupBarrier();
|
| 150 |
|
| 151 |
+
// Fold within each column group. K_LANES is a power of two, so kl + stride
|
| 152 |
+
// never leaves the group.
|
| 153 |
+
for (var stride = K_LANES / 2u; stride > 0u; stride = stride / 2u) {
|
| 154 |
+
if (kl < stride) {
|
|
|
|
|
|
|
| 155 |
partial0[tid] = partial0[tid] + partial0[tid + stride];
|
| 156 |
partial1[tid] = partial1[tid] + partial1[tid + stride];
|
| 157 |
partial2[tid] = partial2[tid] + partial2[tid + stride];
|
| 158 |
partial3[tid] = partial3[tid] + partial3[tid + stride];
|
| 159 |
}
|
|
|
|
| 160 |
workgroupBarrier();
|
| 161 |
}
|
| 162 |
|
| 163 |
+
if (kl == 0u && group_valid) {
|
| 164 |
{% if hasBias %}
|
| 165 |
let biasv = vec4<f32>(
|
| 166 |
f32(bias[col_base + 0u]),
|
| 167 |
+
select(0.0, f32(bias[min(col_base + 1u, params.N - 1u)]), col1),
|
| 168 |
+
select(0.0, f32(bias[min(col_base + 2u, params.N - 1u)]), col2),
|
| 169 |
+
select(0.0, f32(bias[min(col_base + 3u, params.N - 1u)]), col3)
|
| 170 |
);
|
| 171 |
+
store_row(row_base + 0u, col_base, partial0[tid] + biasv);
|
| 172 |
+
store_row(row_base + 1u, col_base, partial1[tid] + biasv);
|
| 173 |
+
store_row(row_base + 2u, col_base, partial2[tid] + biasv);
|
| 174 |
+
store_row(row_base + 3u, col_base, partial3[tid] + biasv);
|
| 175 |
{% else %}
|
| 176 |
+
store_row(row_base + 0u, col_base, partial0[tid]);
|
| 177 |
+
store_row(row_base + 1u, col_base, partial1[tid]);
|
| 178 |
+
store_row(row_base + 2u, col_base, partial2[tid]);
|
| 179 |
+
store_row(row_base + 3u, col_base, partial3[tid]);
|
| 180 |
{% endif %}
|
| 181 |
}
|
| 182 |
}
|
build/webgpu/matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja
CHANGED
|
@@ -1,6 +1,3 @@
|
|
| 1 |
-
{% if usesF16 %}
|
| 2 |
-
enable f16;
|
| 3 |
-
{% endif %}
|
| 4 |
{{ env.wgsl.resourceDeclarations }}
|
| 5 |
|
| 6 |
// com.microsoft.MatMulNBits q4/q8 prefill, no-subgroup-matrix tier — register-blocked.
|
|
@@ -30,6 +27,13 @@ const TN: u32 = {{ threadCols }}u;
|
|
| 30 |
const WG_X: u32 = 16u;
|
| 31 |
const WG_Y: u32 = 16u;
|
| 32 |
const WG_THREADS: u32 = WG_X * WG_Y;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
// When A uses f16 storage, staging tileA at f16 is bit-identical to widening it
|
| 35 |
// here. tileB stays f32 because the dequantized (code - zero)*scale product
|
|
@@ -46,18 +50,16 @@ var<workgroup> tileB: array<array<vec4<f32>, K_VECS>, BN>; // [BN][BK/4]
|
|
| 46 |
{% macro matmul_nbits_packed_code(fn="packed_weight", buffer="b", kBlocks="params.kBlocks", blobSize="params.blobSize", bits=4) %}
|
| 47 |
fn {{ fn }}(n: u32, block: u32, offset: u32) -> u32 {
|
| 48 |
{% if bits == 2 %}
|
| 49 |
-
let byte_index = offset
|
| 50 |
-
let shift = (offset
|
| 51 |
-
|
| 52 |
-
return ({{ buffer }}[packed_index] >> shift) & 3u;
|
| 53 |
{% elif bits == 4 %}
|
| 54 |
-
let byte_index = offset
|
| 55 |
-
let shift = (offset
|
| 56 |
-
|
| 57 |
-
return ({{ buffer }}[packed_index] >> shift) & 15u;
|
| 58 |
{% else %}
|
| 59 |
-
let
|
| 60 |
-
return {{ buffer }}[
|
| 61 |
{% endif %}
|
| 62 |
}
|
| 63 |
{%- endmacro %}
|
|
@@ -82,7 +84,13 @@ fn main(
|
|
| 82 |
{% endfor %}
|
| 83 |
|
| 84 |
let numTiles = (K + BK - 1u) / BK;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
|
|
|
|
| 86 |
let kBase = kt * BK;
|
| 87 |
// A and B may have different tile areas (32x64 portable geometry), so stage
|
| 88 |
// them independently. Every configured area is an exact WG_THREADS multiple.
|
|
@@ -92,17 +100,26 @@ fn main(
|
|
| 92 |
let am = mBase + ar;
|
| 93 |
let ak = kBase + ac4 * 4u;
|
| 94 |
var aWord = vec4<{{ aTile }}>({{ aTile }}(0.0));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
if (am < M) {
|
| 96 |
{% for component in range(4) %}
|
| 97 |
if (ak + {{ component }}u < K) { aWord[{{ component }}u] = {{ aTile }}(a[am * K + ak + {{ component }}u]); }
|
| 98 |
{% endfor %}
|
| 99 |
}
|
|
|
|
| 100 |
tileA[ar][ac4] = aWord;
|
| 101 |
}
|
| 102 |
{% if alignedBlockLoads %}
|
| 103 |
// Every lane materializes one vector word of one output column. BLOCK_SIZE is
|
| 104 |
// a multiple of BK, so the whole tile slice of a column shares one scale and
|
| 105 |
-
// zero point
|
|
|
|
|
|
|
| 106 |
for (var idx: u32 = li; idx < BN * K_VECS; idx = idx + WG_THREADS) {
|
| 107 |
let bc = idx / K_VECS;
|
| 108 |
let kv = idx % K_VECS;
|
|
@@ -116,9 +133,9 @@ fn main(
|
|
| 116 |
let blob_base = (bn * KBLOCKS + block) * BLOB_SIZE;
|
| 117 |
let kk = kv * 4u;
|
| 118 |
{% if bits == 2 %}
|
| 119 |
-
// Four 2-bit codes share a stored byte
|
| 120 |
-
|
| 121 |
-
let packed = b[
|
| 122 |
{% for component in range(4) %}
|
| 123 |
if (kBase + kk + {{ component }}u < K) {
|
| 124 |
word[{{ component }}u] = (f32((packed >> {{ component * 2 }}u) & 3u) - zero) * scale;
|
|
@@ -126,19 +143,19 @@ fn main(
|
|
| 126 |
{% endfor %}
|
| 127 |
{% elif bits == 4 %}
|
| 128 |
// Two bytes carry the four K-adjacent codes of one vector word.
|
| 129 |
-
let byte_index = blob_base + (offset0 + kk)
|
| 130 |
-
let
|
| 131 |
-
let packed1 = b[byte_index + 1u];
|
| 132 |
{% for component in range(4) %}
|
| 133 |
if (kBase + kk + {{ component }}u < K) {
|
| 134 |
-
word[{{ component }}u] = (f32((
|
| 135 |
}
|
| 136 |
{% endfor %}
|
| 137 |
{% else %}
|
| 138 |
let byte_index = blob_base + offset0 + kk;
|
|
|
|
| 139 |
{% for component in range(4) %}
|
| 140 |
if (kBase + kk + {{ component }}u < K) {
|
| 141 |
-
word[{{ component }}u] = (f32(
|
| 142 |
}
|
| 143 |
{% endfor %}
|
| 144 |
{% endif %}
|
|
@@ -192,7 +209,11 @@ fn main(
|
|
| 192 |
if (m0 + {{ i }}u < M) {
|
| 193 |
{% for j in range(threadCols) %}
|
| 194 |
if (n0 + {{ j }}u < N) {
|
|
|
|
|
|
|
|
|
|
| 195 |
y[(m0 + {{ i }}u) * N + n0 + {{ j }}u] = {{ outputScalar }}(acc{{ i }}{{ j }}{% if hasBias %} + f32(bias[n0 + {{ j }}u]){% endif %});
|
|
|
|
| 196 |
}
|
| 197 |
{% endfor %}
|
| 198 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
{{ env.wgsl.resourceDeclarations }}
|
| 2 |
|
| 3 |
// com.microsoft.MatMulNBits q4/q8 prefill, no-subgroup-matrix tier — register-blocked.
|
|
|
|
| 27 |
const WG_X: u32 = 16u;
|
| 28 |
const WG_Y: u32 = 16u;
|
| 29 |
const WG_THREADS: u32 = WG_X * WG_Y;
|
| 30 |
+
{% set splitKValue = splitK if splitK is defined else 1 %}
|
| 31 |
+
{% set tilesPerSplitValue = tilesPerSplit if tilesPerSplit is defined else 0 %}
|
| 32 |
+
{% if splitKValue > 1 %}
|
| 33 |
+
// Split-K: dispatch.z owns SPLIT_TILES consecutive K tiles and writes an f32
|
| 34 |
+
// partial block for the combine pass (bias, if any, is added there).
|
| 35 |
+
const SPLIT_TILES: u32 = {{ tilesPerSplitValue }}u;
|
| 36 |
+
{% endif %}
|
| 37 |
|
| 38 |
// When A uses f16 storage, staging tileA at f16 is bit-identical to widening it
|
| 39 |
// here. tileB stays f32 because the dequantized (code - zero)*scale product
|
|
|
|
| 50 |
{% macro matmul_nbits_packed_code(fn="packed_weight", buffer="b", kBlocks="params.kBlocks", blobSize="params.blobSize", bits=4) %}
|
| 51 |
fn {{ fn }}(n: u32, block: u32, offset: u32) -> u32 {
|
| 52 |
{% if bits == 2 %}
|
| 53 |
+
let byte_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + (offset >> 2u);
|
| 54 |
+
let shift = (byte_index & 3u) * 8u + (offset & 3u) * 2u;
|
| 55 |
+
return ({{ buffer }}[byte_index >> 2u] >> shift) & 3u;
|
|
|
|
| 56 |
{% elif bits == 4 %}
|
| 57 |
+
let byte_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + (offset >> 1u);
|
| 58 |
+
let shift = (byte_index & 3u) * 8u + (offset & 1u) * 4u;
|
| 59 |
+
return ({{ buffer }}[byte_index >> 2u] >> shift) & 15u;
|
|
|
|
| 60 |
{% else %}
|
| 61 |
+
let byte_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + offset;
|
| 62 |
+
return ({{ buffer }}[byte_index >> 2u] >> ((byte_index & 3u) * 8u)) & 255u;
|
| 63 |
{% endif %}
|
| 64 |
}
|
| 65 |
{%- endmacro %}
|
|
|
|
| 84 |
{% endfor %}
|
| 85 |
|
| 86 |
let numTiles = (K + BK - 1u) / BK;
|
| 87 |
+
{% if splitKValue > 1 %}
|
| 88 |
+
let ktBegin = wg.z * SPLIT_TILES;
|
| 89 |
+
let ktEnd = min(numTiles, ktBegin + SPLIT_TILES);
|
| 90 |
+
for (var kt: u32 = ktBegin; kt < ktEnd; kt = kt + 1u) {
|
| 91 |
+
{% else %}
|
| 92 |
for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
|
| 93 |
+
{% endif %}
|
| 94 |
let kBase = kt * BK;
|
| 95 |
// A and B may have different tile areas (32x64 portable geometry), so stage
|
| 96 |
// them independently. Every configured area is an exact WG_THREADS multiple.
|
|
|
|
| 100 |
let am = mBase + ar;
|
| 101 |
let ak = kBase + ac4 * 4u;
|
| 102 |
var aWord = vec4<{{ aTile }}>({{ aTile }}(0.0));
|
| 103 |
+
{% if aVec4Loads is defined and aVec4Loads %}
|
| 104 |
+
// K is a multiple of four, so every staged word is one aligned vector load.
|
| 105 |
+
if (am < M) {
|
| 106 |
+
aWord = vec4<{{ aTile }}>(a[(am * K + ak) / 4u]);
|
| 107 |
+
}
|
| 108 |
+
{% else %}
|
| 109 |
if (am < M) {
|
| 110 |
{% for component in range(4) %}
|
| 111 |
if (ak + {{ component }}u < K) { aWord[{{ component }}u] = {{ aTile }}(a[am * K + ak + {{ component }}u]); }
|
| 112 |
{% endfor %}
|
| 113 |
}
|
| 114 |
+
{% endif %}
|
| 115 |
tileA[ar][ac4] = aWord;
|
| 116 |
}
|
| 117 |
{% if alignedBlockLoads %}
|
| 118 |
// Every lane materializes one vector word of one output column. BLOCK_SIZE is
|
| 119 |
// a multiple of BK, so the whole tile slice of a column shares one scale and
|
| 120 |
+
// zero point. The blob is packed four bytes per u32 word and the four
|
| 121 |
+
// K-adjacent codes of a vector word are byte-contiguous from a 4-aligned
|
| 122 |
+
// offset, so they always come out of ONE packed word: one load per vector.
|
| 123 |
for (var idx: u32 = li; idx < BN * K_VECS; idx = idx + WG_THREADS) {
|
| 124 |
let bc = idx / K_VECS;
|
| 125 |
let kv = idx % K_VECS;
|
|
|
|
| 133 |
let blob_base = (bn * KBLOCKS + block) * BLOB_SIZE;
|
| 134 |
let kk = kv * 4u;
|
| 135 |
{% if bits == 2 %}
|
| 136 |
+
// Four 2-bit codes share a stored byte.
|
| 137 |
+
let byte_index = blob_base + ((offset0 + kk) >> 2u);
|
| 138 |
+
let packed = b[byte_index >> 2u] >> ((byte_index & 3u) * 8u);
|
| 139 |
{% for component in range(4) %}
|
| 140 |
if (kBase + kk + {{ component }}u < K) {
|
| 141 |
word[{{ component }}u] = (f32((packed >> {{ component * 2 }}u) & 3u) - zero) * scale;
|
|
|
|
| 143 |
{% endfor %}
|
| 144 |
{% elif bits == 4 %}
|
| 145 |
// Two bytes carry the four K-adjacent codes of one vector word.
|
| 146 |
+
let byte_index = blob_base + ((offset0 + kk) >> 1u);
|
| 147 |
+
let packed = b[byte_index >> 2u] >> ((byte_index & 3u) * 8u);
|
|
|
|
| 148 |
{% for component in range(4) %}
|
| 149 |
if (kBase + kk + {{ component }}u < K) {
|
| 150 |
+
word[{{ component }}u] = (f32((packed >> {{ component * 4 }}u) & 15u) - zero) * scale;
|
| 151 |
}
|
| 152 |
{% endfor %}
|
| 153 |
{% else %}
|
| 154 |
let byte_index = blob_base + offset0 + kk;
|
| 155 |
+
let packed = b[byte_index >> 2u];
|
| 156 |
{% for component in range(4) %}
|
| 157 |
if (kBase + kk + {{ component }}u < K) {
|
| 158 |
+
word[{{ component }}u] = (f32((packed >> {{ component * 8 }}u) & 255u) - zero) * scale;
|
| 159 |
}
|
| 160 |
{% endfor %}
|
| 161 |
{% endif %}
|
|
|
|
| 209 |
if (m0 + {{ i }}u < M) {
|
| 210 |
{% for j in range(threadCols) %}
|
| 211 |
if (n0 + {{ j }}u < N) {
|
| 212 |
+
{% if splitKValue > 1 %}
|
| 213 |
+
y[wg.z * M * N + (m0 + {{ i }}u) * N + n0 + {{ j }}u] = acc{{ i }}{{ j }};
|
| 214 |
+
{% else %}
|
| 215 |
y[(m0 + {{ i }}u) * N + n0 + {{ j }}u] = {{ outputScalar }}(acc{{ i }}{{ j }}{% if hasBias %} + f32(bias[n0 + {{ j }}u]){% endif %});
|
| 216 |
+
{% endif %}
|
| 217 |
}
|
| 218 |
{% endfor %}
|
| 219 |
}
|
build/webgpu/matmul-nbits-q4-prefill-tiled.wgsl.jinja
CHANGED
|
@@ -1,6 +1,3 @@
|
|
| 1 |
-
{% if usesF16 %}
|
| 2 |
-
enable f16;
|
| 3 |
-
{% endif %}
|
| 4 |
{{ env.wgsl.resourceDeclarations }}
|
| 5 |
|
| 6 |
// com.microsoft.MatMulNBits q4/q8 prefill, no-subgroup-matrix tier:
|
|
@@ -8,9 +5,9 @@ enable f16;
|
|
| 8 |
// Register-blocked 32x32 shared-memory tiled GEMM (16x16 threads, each computes a
|
| 9 |
// 2x2 micro-tile, K stepped in BK=16 chunks). The B operand is dequantized from
|
| 10 |
// the packed blob straight into the shared B tile (no f32 weight matrix stored).
|
| 11 |
-
// Each staged operand is reused across the micro-tile
|
| 12 |
-
//
|
| 13 |
-
//
|
| 14 |
const M: u32 = {{ M }}u;
|
| 15 |
const K: u32 = {{ K }}u;
|
| 16 |
const N: u32 = {{ N }}u;
|
|
@@ -34,18 +31,16 @@ var<workgroup> tileB: array<array<f32, 32>, 16>;
|
|
| 34 |
{% macro matmul_nbits_packed_code(fn="packed_weight", buffer="b", kBlocks="params.kBlocks", blobSize="params.blobSize", bits=4) %}
|
| 35 |
fn {{ fn }}(n: u32, block: u32, offset: u32) -> u32 {
|
| 36 |
{% if bits == 2 %}
|
| 37 |
-
let byte_index = offset
|
| 38 |
-
let shift = (offset
|
| 39 |
-
|
| 40 |
-
return ({{ buffer }}[packed_index] >> shift) & 3u;
|
| 41 |
{% elif bits == 4 %}
|
| 42 |
-
let byte_index = offset
|
| 43 |
-
let shift = (offset
|
| 44 |
-
|
| 45 |
-
return ({{ buffer }}[packed_index] >> shift) & 15u;
|
| 46 |
{% else %}
|
| 47 |
-
let
|
| 48 |
-
return {{ buffer }}[
|
| 49 |
{% endif %}
|
| 50 |
}
|
| 51 |
{%- endmacro %}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
{{ env.wgsl.resourceDeclarations }}
|
| 2 |
|
| 3 |
// com.microsoft.MatMulNBits q4/q8 prefill, no-subgroup-matrix tier:
|
|
|
|
| 5 |
// Register-blocked 32x32 shared-memory tiled GEMM (16x16 threads, each computes a
|
| 6 |
// 2x2 micro-tile, K stepped in BK=16 chunks). The B operand is dequantized from
|
| 7 |
// the packed blob straight into the shared B tile (no f32 weight matrix stored).
|
| 8 |
+
// Each staged operand is reused across the micro-tile. Dequantized weights and
|
| 9 |
+
// accumulations use f32. Bounds checks support any M and N when K is divisible
|
| 10 |
+
// by blockSize.
|
| 11 |
const M: u32 = {{ M }}u;
|
| 12 |
const K: u32 = {{ K }}u;
|
| 13 |
const N: u32 = {{ N }}u;
|
|
|
|
| 31 |
{% macro matmul_nbits_packed_code(fn="packed_weight", buffer="b", kBlocks="params.kBlocks", blobSize="params.blobSize", bits=4) %}
|
| 32 |
fn {{ fn }}(n: u32, block: u32, offset: u32) -> u32 {
|
| 33 |
{% if bits == 2 %}
|
| 34 |
+
let byte_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + (offset >> 2u);
|
| 35 |
+
let shift = (byte_index & 3u) * 8u + (offset & 3u) * 2u;
|
| 36 |
+
return ({{ buffer }}[byte_index >> 2u] >> shift) & 3u;
|
|
|
|
| 37 |
{% elif bits == 4 %}
|
| 38 |
+
let byte_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + (offset >> 1u);
|
| 39 |
+
let shift = (byte_index & 3u) * 8u + (offset & 1u) * 4u;
|
| 40 |
+
return ({{ buffer }}[byte_index >> 2u] >> shift) & 15u;
|
|
|
|
| 41 |
{% else %}
|
| 42 |
+
let byte_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + offset;
|
| 43 |
+
return ({{ buffer }}[byte_index >> 2u] >> ((byte_index & 3u) * 8u)) & 255u;
|
| 44 |
{% endif %}
|
| 45 |
}
|
| 46 |
{%- endmacro %}
|
build/webgpu/matmul-nbits-q4-sgmat.wgsl.jinja
CHANGED
|
@@ -2,9 +2,8 @@
|
|
| 2 |
// Y[M,N] = A[M,K] @ W[K,N] + bias?, W[k,n] = (q4(n,block,offset) - zero) * scale
|
| 3 |
// Uses a 32x64 or 64x64 subgroup-matrix output tile with an M-tail row_limit guard; the
|
| 4 |
// B operand is materialized on the fly by dequantizing the packed q4 blob into
|
| 5 |
-
// the shared B tile, so no f32 weight matrix is stored.
|
| 6 |
-
//
|
| 7 |
-
// f32 is exact for it.
|
| 8 |
// Alignment gates keep K % 32 == 0 and N % 64 == 0 so subgroupMatrixLoad never
|
| 9 |
// sees partial 8x8 tiles; M tails handled by row_limit + the loadSHMA zero-fill.
|
| 10 |
enable subgroups;
|
|
@@ -12,11 +11,9 @@ enable subgroups;
|
|
| 12 |
enable subgroup_size_control;
|
| 13 |
{% endif %}
|
| 14 |
enable chromium_experimental_subgroup_matrix;
|
| 15 |
-
{% if usesF16 %}
|
| 16 |
-
enable f16;
|
| 17 |
-
{% endif %}
|
| 18 |
diagnostic(off, chromium.subgroup_matrix_uniformity);
|
| 19 |
|
|
|
|
| 20 |
{{ env.wgsl.resourceDeclarations }}
|
| 21 |
|
| 22 |
const M: u32 = {{ M }}u;
|
|
@@ -68,25 +65,31 @@ fn loadSHMB(tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
|
|
| 68 |
{% else %}
|
| 69 |
let zero = {{ defaultZero }};
|
| 70 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
{% if bits == 2 %}
|
| 72 |
-
//
|
| 73 |
-
// about where offset0 lands inside a byte.
|
| 74 |
for (var i = 0u; i < {{ bLoadWidth }}u; i = i + 1u) {
|
| 75 |
let code_offset = offset0 + i;
|
| 76 |
-
let
|
| 77 |
-
|
|
|
|
| 78 |
}
|
| 79 |
{% elif bits == 4 %}
|
| 80 |
-
for (var i = 0u; i < {{ bLoadWidth }}u; i = i +
|
| 81 |
-
let
|
| 82 |
-
|
| 83 |
-
tile_B[row * TILE_K + col + i] = (f32(packed & 15u) - zero) * scale;
|
| 84 |
-
|
| 85 |
}
|
| 86 |
{% else %}
|
| 87 |
-
for (var i = 0u; i < {{ bLoadWidth }}u; i = i +
|
| 88 |
-
let
|
| 89 |
-
|
|
|
|
|
|
|
| 90 |
}
|
| 91 |
{% endif %}
|
| 92 |
} else {
|
|
@@ -147,15 +150,16 @@ fn main(
|
|
| 147 |
workgroupBarrier();
|
| 148 |
|
| 149 |
for (var step = 0u; step < TILE_K; step = step + 8u) {
|
| 150 |
-
{% set operandScalar = "f32" %}
|
| 151 |
-
|
| 152 |
-
|
|
|
|
|
|
|
| 153 |
|
| 154 |
let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
|
| 155 |
-
|
| 156 |
-
var
|
| 157 |
-
|
| 158 |
-
var matB3: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 24u * TILE_K, TILE_K);
|
| 159 |
|
| 160 |
matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
|
| 161 |
matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
|
|
|
|
| 2 |
// Y[M,N] = A[M,K] @ W[K,N] + bias?, W[k,n] = (q4(n,block,offset) - zero) * scale
|
| 3 |
// Uses a 32x64 or 64x64 subgroup-matrix output tile with an M-tail row_limit guard; the
|
| 4 |
// B operand is materialized on the fly by dequantizing the packed q4 blob into
|
| 5 |
+
// the shared B tile, so no f32 weight matrix is stored. Dequantized operands and
|
| 6 |
+
// accumulations use f32.
|
|
|
|
| 7 |
// Alignment gates keep K % 32 == 0 and N % 64 == 0 so subgroupMatrixLoad never
|
| 8 |
// sees partial 8x8 tiles; M tails handled by row_limit + the loadSHMA zero-fill.
|
| 9 |
enable subgroups;
|
|
|
|
| 11 |
enable subgroup_size_control;
|
| 12 |
{% endif %}
|
| 13 |
enable chromium_experimental_subgroup_matrix;
|
|
|
|
|
|
|
|
|
|
| 14 |
diagnostic(off, chromium.subgroup_matrix_uniformity);
|
| 15 |
|
| 16 |
+
|
| 17 |
{{ env.wgsl.resourceDeclarations }}
|
| 18 |
|
| 19 |
const M: u32 = {{ M }}u;
|
|
|
|
| 65 |
{% else %}
|
| 66 |
let zero = {{ defaultZero }};
|
| 67 |
{% endif %}
|
| 68 |
+
// The blob is packed four bytes per u32 word. The slice starts on an 8-code
|
| 69 |
+
// boundary inside a 4-byte-aligned blob, so every packed word below holds
|
| 70 |
+
// 32 / bits consecutive codes of this column and is read once.
|
| 71 |
+
let blob_base = (n * KBLOCKS + block) * BLOB_SIZE;
|
| 72 |
{% if bits == 2 %}
|
| 73 |
+
// Sixteen 2-bit codes per word; the slice may start mid-word.
|
|
|
|
| 74 |
for (var i = 0u; i < {{ bLoadWidth }}u; i = i + 1u) {
|
| 75 |
let code_offset = offset0 + i;
|
| 76 |
+
let byte_index = blob_base + (code_offset >> 2u);
|
| 77 |
+
let shift = (byte_index & 3u) * 8u + (code_offset & 3u) * 2u;
|
| 78 |
+
tile_B[row * TILE_K + col + i] = (f32((b[byte_index >> 2u] >> shift) & 3u) - zero) * scale;
|
| 79 |
}
|
| 80 |
{% elif bits == 4 %}
|
| 81 |
+
for (var i = 0u; i < {{ bLoadWidth }}u; i = i + 8u) {
|
| 82 |
+
let packed = b[(blob_base + ((offset0 + i) >> 1u)) >> 2u];
|
| 83 |
+
{% for h in range(8) %}
|
| 84 |
+
tile_B[row * TILE_K + col + i + {{ h }}u] = (f32((packed >> {{ h * 4 }}u) & 15u) - zero) * scale;
|
| 85 |
+
{% endfor %}
|
| 86 |
}
|
| 87 |
{% else %}
|
| 88 |
+
for (var i = 0u; i < {{ bLoadWidth }}u; i = i + 4u) {
|
| 89 |
+
let packed = b[(blob_base + offset0 + i) >> 2u];
|
| 90 |
+
{% for h in range(4) %}
|
| 91 |
+
tile_B[row * TILE_K + col + i + {{ h }}u] = (f32((packed >> {{ h * 8 }}u) & 255u) - zero) * scale;
|
| 92 |
+
{% endfor %}
|
| 93 |
}
|
| 94 |
{% endif %}
|
| 95 |
} else {
|
|
|
|
| 150 |
workgroupBarrier();
|
| 151 |
|
| 152 |
for (var step = 0u; step < TILE_K; step = step + 8u) {
|
| 153 |
+
{% set operandScalar = "f32" %}{% set directInputs = directMatrixInputs is defined and directMatrixInputs %}
|
| 154 |
+
let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
|
| 155 |
+
{% for r in range(2) %}
|
| 156 |
+
var matA{{ r }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&{{ "w" if directInputs else "tile_A" }}, matrix_a_offset{% if r > 0 %} + 8u * {{ "K" if directInputs else "TILE_K" }}{% endif %}, {{ "K" if directInputs else "TILE_K" }});
|
| 157 |
+
{% endfor %}
|
| 158 |
|
| 159 |
let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
|
| 160 |
+
{% for c in range(4) %}
|
| 161 |
+
var matB{{ c }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&{{ "xm" if directInputs else "tile_B" }}, matrix_b_offset{% if c > 0 %} + {{ c * 8 }}u * TILE_K{% endif %}, {{ "N" if directInputs else "TILE_K" }});
|
| 162 |
+
{% endfor %}
|
|
|
|
| 163 |
|
| 164 |
matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
|
| 165 |
matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
|
build/webgpu/matmul-nbits.wgsl.jinja
CHANGED
|
@@ -1,6 +1,3 @@
|
|
| 1 |
-
{% if usesF16 %}
|
| 2 |
-
enable f16;
|
| 3 |
-
{% endif %}
|
| 4 |
{{ env.wgsl.resourceDeclarations }}
|
| 5 |
|
| 6 |
const WG: u32 = {{ workgroupSize }}u;
|
|
@@ -12,29 +9,27 @@ const WG: u32 = {{ workgroupSize }}u;
|
|
| 12 |
{% macro matmul_nbits_packed_code(fn="packed_weight", buffer="b", kBlocks="params.kBlocks", blobSize="params.blobSize", bits=4) %}
|
| 13 |
fn {{ fn }}(n: u32, block: u32, offset: u32) -> u32 {
|
| 14 |
{% if bits == 2 %}
|
| 15 |
-
let byte_index = offset
|
| 16 |
-
let shift = (offset
|
| 17 |
-
|
| 18 |
-
return ({{ buffer }}[packed_index] >> shift) & 3u;
|
| 19 |
{% elif bits == 4 %}
|
| 20 |
-
let byte_index = offset
|
| 21 |
-
let shift = (offset
|
| 22 |
-
|
| 23 |
-
return ({{ buffer }}[packed_index] >> shift) & 15u;
|
| 24 |
{% else %}
|
| 25 |
-
let
|
| 26 |
-
return {{ buffer }}[
|
| 27 |
{% endif %}
|
| 28 |
}
|
| 29 |
{%- endmacro %}
|
| 30 |
{{- matmul_nbits_packed_code(bits=bits) }}
|
| 31 |
|
| 32 |
@compute @workgroup_size(WG, 1, 1)
|
| 33 |
-
fn main(@builtin(global_invocation_id) gid: vec3<u32>
|
| 34 |
// 2D-folded flat output-element index: gid.y carries the high bits past the
|
| 35 |
-
//
|
| 36 |
// reduces to gid.x; the index >= total guard drops the tail.
|
| 37 |
-
let index = gid.x + gid.y *
|
| 38 |
let total = params.rows * params.N;
|
| 39 |
|
| 40 |
if (index >= total) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
{{ env.wgsl.resourceDeclarations }}
|
| 2 |
|
| 3 |
const WG: u32 = {{ workgroupSize }}u;
|
|
|
|
| 9 |
{% macro matmul_nbits_packed_code(fn="packed_weight", buffer="b", kBlocks="params.kBlocks", blobSize="params.blobSize", bits=4) %}
|
| 10 |
fn {{ fn }}(n: u32, block: u32, offset: u32) -> u32 {
|
| 11 |
{% if bits == 2 %}
|
| 12 |
+
let byte_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + (offset >> 2u);
|
| 13 |
+
let shift = (byte_index & 3u) * 8u + (offset & 3u) * 2u;
|
| 14 |
+
return ({{ buffer }}[byte_index >> 2u] >> shift) & 3u;
|
|
|
|
| 15 |
{% elif bits == 4 %}
|
| 16 |
+
let byte_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + (offset >> 1u);
|
| 17 |
+
let shift = (byte_index & 3u) * 8u + (offset & 1u) * 4u;
|
| 18 |
+
return ({{ buffer }}[byte_index >> 2u] >> shift) & 15u;
|
|
|
|
| 19 |
{% else %}
|
| 20 |
+
let byte_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + offset;
|
| 21 |
+
return ({{ buffer }}[byte_index >> 2u] >> ((byte_index & 3u) * 8u)) & 255u;
|
| 22 |
{% endif %}
|
| 23 |
}
|
| 24 |
{%- endmacro %}
|
| 25 |
{{- matmul_nbits_packed_code(bits=bits) }}
|
| 26 |
|
| 27 |
@compute @workgroup_size(WG, 1, 1)
|
| 28 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 29 |
// 2D-folded flat output-element index: gid.y carries the high bits past the
|
| 30 |
+
// per-axis dispatch fold width. With no fold this
|
| 31 |
// reduces to gid.x; the index >= total guard drops the tail.
|
| 32 |
+
let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
|
| 33 |
let total = params.rows * params.N;
|
| 34 |
|
| 35 |
if (index >= total) {
|
build/webgpu/metadata.json
CHANGED
|
@@ -1,25 +1,63 @@
|
|
| 1 |
{
|
| 2 |
"name": "com.microsoft.MatMulNBits",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
-
"bench.json": "
|
| 11 |
-
"manifest.json": "
|
| 12 |
-
"matmul-nbits-dp4a-quantize.wgsl.jinja": "
|
| 13 |
-
"matmul-nbits-gemv-q4.wgsl.jinja": "
|
| 14 |
-
"matmul-nbits-q4-dp4a-prefill.wgsl.jinja": "
|
| 15 |
-
"matmul-nbits-q4-prefill-tile4x4.wgsl.jinja": "
|
| 16 |
-
"matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja": "
|
| 17 |
-
"matmul-nbits-q4-prefill-tiled.wgsl.jinja": "
|
| 18 |
-
"matmul-nbits-q4-sgmat.wgsl.jinja": "
|
| 19 |
-
"matmul-nbits.wgsl.jinja": "
|
| 20 |
-
"
|
|
|
|
| 21 |
}
|
| 22 |
},
|
| 23 |
-
"provenance": { "kernel": { "sha": "
|
| 24 |
-
"webgpu": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "com.microsoft.MatMulNBits",
|
| 3 |
+
"id": "_com_microsoft_matmulnbits_webgpu_6f18c00",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
+
"bench.json": "P8wNyT1cbaSLn1VS4hVRFQxLYwji4Z7gp69Mt2pQaFo=",
|
| 11 |
+
"manifest.json": "QPPdzEGo1lJtp16tQaaqA9UHKPlvBY3fq66gG4XPvyQ=",
|
| 12 |
+
"matmul-nbits-dp4a-quantize.wgsl.jinja": "0gqEvgBRzH2Demz/RvrFkUOV487GyCq7Ujd26GyD+xw=",
|
| 13 |
+
"matmul-nbits-gemv-q4.wgsl.jinja": "ewlLPcW7t3UdnoLymZBoXnV+1oOeB9+YTyaOAnPhhfA=",
|
| 14 |
+
"matmul-nbits-q4-dp4a-prefill.wgsl.jinja": "pd9dWWUdIgYFVCRayU5OlvjYIvyqPvkOGgwtEPNMHas=",
|
| 15 |
+
"matmul-nbits-q4-prefill-tile4x4.wgsl.jinja": "8DVy3szxcxwVItIlEXoioQi2YxDmmd5TCFW4BCYPiCQ=",
|
| 16 |
+
"matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja": "W6JEeRleux7Jm3wmaflEusYFNuCiFtXnBTEW5cfS4Fk=",
|
| 17 |
+
"matmul-nbits-q4-prefill-tiled.wgsl.jinja": "eAEIbpkW0qziqyXhYNRiVbCuDRghjoz8EjnOrP8AgD8=",
|
| 18 |
+
"matmul-nbits-q4-sgmat.wgsl.jinja": "SFEMothi+irkTIclMjeTF1sCmB61Ai/2hGgnoOyNMUM=",
|
| 19 |
+
"matmul-nbits.wgsl.jinja": "UndxgqiV/O19Plpxda1d388lnQWyeCugbBvJGcYr9t8=",
|
| 20 |
+
"reduce-axis0-splitk-combine.wgsl.jinja": "Yz1hjK55R/kndUrw3ugPqgPaOBwKmYupqZoVdJTHO5Q=",
|
| 21 |
+
"test.json": "Hnb4z+ExdwYZb4EjSBR0gdGi5eY1kh7DaoGj3UTC5Ug="
|
| 22 |
}
|
| 23 |
},
|
| 24 |
+
"provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
|
| 25 |
+
"webgpu": {
|
| 26 |
+
"manifestSpec": "2.0",
|
| 27 |
+
"variants": {
|
| 28 |
+
"q4_dp4a_prefill": ["matmul-nbits-dp4a-quantize.wgsl.jinja", "matmul-nbits-q4-dp4a-prefill.wgsl.jinja"],
|
| 29 |
+
"gemv_default_zero": ["matmul-nbits-gemv-q4.wgsl.jinja"],
|
| 30 |
+
"prefill_sgmat_default_zero": ["matmul-nbits-q4-sgmat.wgsl.jinja"],
|
| 31 |
+
"prefill_tiled_reg_vec4_splitk_default_zero": ["matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja", "reduce-axis0-splitk-combine.wgsl.jinja"],
|
| 32 |
+
"prefill_tiled_reg_vec4_default_zero": ["matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja"],
|
| 33 |
+
"prefill_tiled_reg_default_zero": ["matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja"],
|
| 34 |
+
"prefill_tiled_default_zero": ["matmul-nbits-q4-prefill-tiled.wgsl.jinja"],
|
| 35 |
+
"prefill_tile4x4_default_zero": ["matmul-nbits-q4-prefill-tile4x4.wgsl.jinja"],
|
| 36 |
+
"default_zero": ["matmul-nbits.wgsl.jinja"],
|
| 37 |
+
"gemv_zero_bias": ["matmul-nbits-gemv-q4.wgsl.jinja"],
|
| 38 |
+
"prefill_sgmat_zero_bias": ["matmul-nbits-q4-sgmat.wgsl.jinja"],
|
| 39 |
+
"prefill_tiled_reg_vec4_splitk_zero_bias": ["matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja", "reduce-axis0-splitk-combine.wgsl.jinja"],
|
| 40 |
+
"prefill_tiled_reg_vec4_zero_bias": ["matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja"],
|
| 41 |
+
"prefill_tiled_reg_zero_bias": ["matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja"],
|
| 42 |
+
"prefill_tiled_zero_bias": ["matmul-nbits-q4-prefill-tiled.wgsl.jinja"],
|
| 43 |
+
"prefill_tile4x4_zero_bias": ["matmul-nbits-q4-prefill-tile4x4.wgsl.jinja"],
|
| 44 |
+
"zero_bias": ["matmul-nbits.wgsl.jinja"],
|
| 45 |
+
"gemv_zero_only": ["matmul-nbits-gemv-q4.wgsl.jinja"],
|
| 46 |
+
"prefill_sgmat_zero_only": ["matmul-nbits-q4-sgmat.wgsl.jinja"],
|
| 47 |
+
"prefill_tiled_reg_vec4_splitk_zero_only": ["matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja", "reduce-axis0-splitk-combine.wgsl.jinja"],
|
| 48 |
+
"prefill_tiled_reg_vec4_zero_only": ["matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja"],
|
| 49 |
+
"prefill_tiled_reg_zero_only": ["matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja"],
|
| 50 |
+
"prefill_tiled_zero_only": ["matmul-nbits-q4-prefill-tiled.wgsl.jinja"],
|
| 51 |
+
"prefill_tile4x4_zero_only": ["matmul-nbits-q4-prefill-tile4x4.wgsl.jinja"],
|
| 52 |
+
"zero_only": ["matmul-nbits.wgsl.jinja"],
|
| 53 |
+
"gemv_bias_only": ["matmul-nbits-gemv-q4.wgsl.jinja"],
|
| 54 |
+
"prefill_sgmat_bias_only": ["matmul-nbits-q4-sgmat.wgsl.jinja"],
|
| 55 |
+
"prefill_tiled_reg_vec4_splitk_bias_only": ["matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja", "reduce-axis0-splitk-combine.wgsl.jinja"],
|
| 56 |
+
"prefill_tiled_reg_vec4_bias_only": ["matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja"],
|
| 57 |
+
"prefill_tiled_reg_bias_only": ["matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja"],
|
| 58 |
+
"prefill_tiled_bias_only": ["matmul-nbits-q4-prefill-tiled.wgsl.jinja"],
|
| 59 |
+
"prefill_tile4x4_bias_only": ["matmul-nbits-q4-prefill-tile4x4.wgsl.jinja"],
|
| 60 |
+
"bias_only": ["matmul-nbits.wgsl.jinja"]
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
}
|
build/webgpu/reduce-axis0-splitk-combine.wgsl.jinja
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Pass 2 of the split-K column-wise reduction. One invocation per output column
|
| 2 |
+
// folds the segment partials and applies the selected reduction's final step.
|
| 3 |
+
// Segments are folded in ascending order for deterministic results. This order
|
| 4 |
+
// differs from the single-pass reduction but remains within the f32 tolerance.
|
| 5 |
+
{% set addBias = addBias is defined and addBias %}
|
| 6 |
+
{% set biasCols = biasCols | default(0) %}
|
| 7 |
+
{% set intMode = intMode is defined and intMode %}
|
| 8 |
+
{% set yv = "f16(" if outputF16 else "" %}
|
| 9 |
+
{% set vy = ")" if outputF16 else "" %}
|
| 10 |
+
{% if outputF16 %}
|
| 11 |
+
enable f16;
|
| 12 |
+
{% endif %}
|
| 13 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 14 |
+
{% macro wgsl_minmax_identity(name, op, scalar="f32") %}
|
| 15 |
+
/* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
|
| 16 |
+
* evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
|
| 17 |
+
{% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
|
| 18 |
+
fn {{ name }}() -> {{ scalar }} {
|
| 19 |
+
{% if scalar == "i32" %}
|
| 20 |
+
return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
|
| 21 |
+
{% elif scalar == "u32" %}
|
| 22 |
+
return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
|
| 23 |
+
{% else %}
|
| 24 |
+
var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
|
| 25 |
+
return bitcast<f32>(bits);
|
| 26 |
+
{% endif %}
|
| 27 |
+
}
|
| 28 |
+
{%- endmacro %}
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
const WG: u32 = {{ workgroupSize }}u;
|
| 32 |
+
const SPLIT: u32 = {{ split }}u;
|
| 33 |
+
{% if addBias %}
|
| 34 |
+
const BIAS_COLS: u32 = {{ biasCols }}u;
|
| 35 |
+
{% endif %}
|
| 36 |
+
{% if op == "logsumexp" %}
|
| 37 |
+
const F32_MIN: f32 = -3.4028234663852886e38;
|
| 38 |
+
const F32_MAX: f32 = 3.4028234663852886e38;
|
| 39 |
+
|
| 40 |
+
fn is_nan_f32(value: f32) -> bool {
|
| 41 |
+
let bits = bitcast<u32>(value);
|
| 42 |
+
return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
|
| 43 |
+
}
|
| 44 |
+
{% elif op == "max" or op == "min" %}
|
| 45 |
+
{{ wgsl_minmax_identity("reduction_identity", op) }}
|
| 46 |
+
{% endif %}
|
| 47 |
+
|
| 48 |
+
@compute @workgroup_size(WG, 1, 1)
|
| 49 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>,
|
| 50 |
+
@builtin(num_workgroups) nwg: vec3<u32>) {
|
| 51 |
+
let stride = nwg.x * WG;
|
| 52 |
+
let start = (gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG) + gid.x;
|
| 53 |
+
for (var col = start; col < params.cols; col = col + stride) {
|
| 54 |
+
{% if op == "logsumexp" %}
|
| 55 |
+
// Merge SPLIT (segMax, segSumExp) pairs stably; carry NaN / +Inf markers.
|
| 56 |
+
var nan_value = 0.0;
|
| 57 |
+
var has_nan = false;
|
| 58 |
+
var global_max = F32_MIN;
|
| 59 |
+
for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
|
| 60 |
+
let nv = partials[(2u * SPLIT + seg) * params.cols + col];
|
| 61 |
+
if (nv != 0.0 || is_nan_f32(nv)) {
|
| 62 |
+
has_nan = true;
|
| 63 |
+
nan_value = nv;
|
| 64 |
+
}
|
| 65 |
+
global_max = max(global_max, partials[seg * params.cols + col]);
|
| 66 |
+
}
|
| 67 |
+
var sum = 0.0;
|
| 68 |
+
for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
|
| 69 |
+
let seg_max = partials[seg * params.cols + col];
|
| 70 |
+
let seg_sum = partials[(SPLIT + seg) * params.cols + col];
|
| 71 |
+
sum = sum + seg_sum * exp(seg_max - global_max);
|
| 72 |
+
}
|
| 73 |
+
let has_positive_inf = global_max > F32_MAX;
|
| 74 |
+
let finite_or_inf = select(global_max + log(sum), global_max, has_positive_inf);
|
| 75 |
+
y[col] = {{ yv }}select(finite_or_inf, nan_value, has_nan){{ vy }};
|
| 76 |
+
{% else %}
|
| 77 |
+
{% if intMode %}
|
| 78 |
+
{% if op == "prod" %}
|
| 79 |
+
var total = 1i;
|
| 80 |
+
{% else %}
|
| 81 |
+
var total = 0i;
|
| 82 |
+
{% endif %}
|
| 83 |
+
{% else %}
|
| 84 |
+
{% if op == "max" %}
|
| 85 |
+
var total = reduction_identity();
|
| 86 |
+
{% elif op == "min" %}
|
| 87 |
+
var total = reduction_identity();
|
| 88 |
+
{% elif op == "prod" %}
|
| 89 |
+
var total = 1.0;
|
| 90 |
+
{% else %}
|
| 91 |
+
var total = 0.0;
|
| 92 |
+
{% endif %}
|
| 93 |
+
{% endif %}
|
| 94 |
+
for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
|
| 95 |
+
let p = partials[seg * params.cols + col];
|
| 96 |
+
{% if op == "max" or op == "min" %}
|
| 97 |
+
total = {{ op }}(total, p);
|
| 98 |
+
{% elif op == "prod" %}
|
| 99 |
+
total = total * p;
|
| 100 |
+
{% else %}
|
| 101 |
+
total = total + p;
|
| 102 |
+
{% endif %}
|
| 103 |
+
}
|
| 104 |
+
{% if addBias %}
|
| 105 |
+
total = total + f32(bias[col % BIAS_COLS]);
|
| 106 |
+
{% endif %}
|
| 107 |
+
{% if op == "l2" %}
|
| 108 |
+
y[col] = {{ yv }}sqrt(total){{ vy }};
|
| 109 |
+
{% elif op == "logsum" %}
|
| 110 |
+
y[col] = {{ yv }}log(total){{ vy }};
|
| 111 |
+
{% elif op == "mean" %}
|
| 112 |
+
y[col] = {{ yv }}total / f32(params.rows){{ vy }};
|
| 113 |
+
{% else %}
|
| 114 |
+
{% if outputF16 %}
|
| 115 |
+
y[col] = f16(total);
|
| 116 |
+
{% else %}
|
| 117 |
+
y[col] = total;
|
| 118 |
+
{% endif %}
|
| 119 |
+
{% endif %}
|
| 120 |
+
{% endif %}
|
| 121 |
+
}
|
| 122 |
+
}
|
build/webgpu/test.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
{
|
| 2 |
-
"op": "com.microsoft.MatMulNBits",
|
| 3 |
"fixtureArrays": {
|
| 4 |
"q4_prefill_tiled_reg_tailk_m32_k33_n4096_input_bT": [16, 50, 84, 118, 152, 186, 220, 254, 135, 119, 102, 85, 68, 51, 34, 17],
|
| 5 |
"q4_weight_cycle_b_t": [16, 50, 84, 118, 152, 186, 220, 254, 33, 67, 101, 135, 169, 203, 237, 15],
|
|
@@ -78,7 +77,7 @@
|
|
| 78 |
{
|
| 79 |
"name": "q8_zero_bias_prefill_tile4x4_small_m8",
|
| 80 |
"provenance": {
|
| 81 |
-
"notes": "
|
| 82 |
},
|
| 83 |
"inputs": {
|
| 84 |
"aT": {
|
|
@@ -181,7 +180,7 @@
|
|
| 181 |
"provenance": {
|
| 182 |
"source": "onnxruntime/test/contrib_ops/matmul_4bits_test.cc",
|
| 183 |
"test": "MatMulNBits.UnsupportedBlockSize_512",
|
| 184 |
-
"notes": "The
|
| 185 |
},
|
| 186 |
"inputs": {
|
| 187 |
"aT": { "dtype": "float32", "shape": [1, 512], "data": { "kind": "constant", "value": 1.0 } },
|
|
@@ -242,7 +241,9 @@
|
|
| 242 |
},
|
| 243 |
{
|
| 244 |
"name": "q4_zero_bias_prefill_sgmat_m32_storage_floor",
|
| 245 |
-
"provenance": {
|
|
|
|
|
|
|
| 246 |
"inputs": {
|
| 247 |
"aT": {
|
| 248 |
"dtype": "float32",
|
|
@@ -276,7 +277,7 @@
|
|
| 276 |
{
|
| 277 |
"name": "q4_no_zero_prefill_g64_sgmat",
|
| 278 |
"provenance": {
|
| 279 |
-
"notes": "
|
| 280 |
},
|
| 281 |
"inputs": {
|
| 282 |
"aT": {
|
|
@@ -323,7 +324,7 @@
|
|
| 323 |
{
|
| 324 |
"name": "q8_no_zero_prefill_sgmat_m32_storage_floor",
|
| 325 |
"provenance": {
|
| 326 |
-
"notes": "q8
|
| 327 |
},
|
| 328 |
"inputs": {
|
| 329 |
"aT": {
|
|
@@ -377,9 +378,131 @@
|
|
| 377 |
"outputs": { "yT": { "dtype": "float32", "shape": [128, 64], "tolerance": 0.001 } },
|
| 378 |
"attrs": { "K": 128, "N": 64, "bits": 8, "block_size": 32 }
|
| 379 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
{
|
| 381 |
"name": "q8_zero_bias_prefill_sgmat_m32_storage_floor",
|
| 382 |
-
"provenance": {
|
|
|
|
|
|
|
| 383 |
"inputs": {
|
| 384 |
"aT": {
|
| 385 |
"dtype": "float32",
|
|
@@ -692,9 +815,7 @@
|
|
| 692 |
}
|
| 693 |
},
|
| 694 |
"outputs": { "yT": { "dtype": "float16", "shape": [512, 1024], "tolerance": 0.0001, "relTolerance": 0.001 } },
|
| 695 |
-
"provenance": {
|
| 696 |
-
"notes": "f16 activations on the register-tiled prefill path: this is the only shape class where the shared A/B tiles are staged in f16, so it is the fixture that covers that staging."
|
| 697 |
-
},
|
| 698 |
"attrs": { "K": 128, "N": 1024, "bits": 4, "block_size": 32 }
|
| 699 |
},
|
| 700 |
{
|
|
@@ -772,7 +893,7 @@
|
|
| 772 |
},
|
| 773 |
"outputs": { "yT": { "dtype": "float16", "shape": [512, 1024], "tolerance": 0.0001, "relTolerance": 0.001 } },
|
| 774 |
"provenance": {
|
| 775 |
-
"notes": "
|
| 776 |
},
|
| 777 |
"attrs": { "K": 128, "N": 1024, "bits": 8, "block_size": 32 }
|
| 778 |
},
|
|
@@ -836,7 +957,7 @@
|
|
| 836 |
{
|
| 837 |
"name": "q4_gemv_default_zero_m1_tail_n7",
|
| 838 |
"provenance": {
|
| 839 |
-
"notes": "M=1
|
| 840 |
},
|
| 841 |
"inputs": {
|
| 842 |
"aT": {
|
|
@@ -861,7 +982,7 @@
|
|
| 861 |
{
|
| 862 |
"name": "q4_gemv_default_zero_m1_odd_k33_n5",
|
| 863 |
"provenance": {
|
| 864 |
-
"notes": "K=33
|
| 865 |
},
|
| 866 |
"inputs": {
|
| 867 |
"aT": {
|
|
@@ -886,7 +1007,7 @@
|
|
| 886 |
{
|
| 887 |
"name": "q2_gemv_default_zero_m1_k18_n5",
|
| 888 |
"provenance": {
|
| 889 |
-
"notes": "
|
| 890 |
},
|
| 891 |
"inputs": {
|
| 892 |
"aT": {
|
|
@@ -938,7 +1059,7 @@
|
|
| 938 |
"name": "q4_gemv_default_zero_m1_tail_n7_ncols8",
|
| 939 |
"tunables": { "GEMV_N_COLS": 8 },
|
| 940 |
"provenance": {
|
| 941 |
-
"notes": "GEMV_N_COLS=8
|
| 942 |
},
|
| 943 |
"inputs": {
|
| 944 |
"aT": {
|
|
@@ -963,7 +1084,7 @@
|
|
| 963 |
{
|
| 964 |
"name": "q8_zero_bias_gemv_m1_tail_n5",
|
| 965 |
"provenance": {
|
| 966 |
-
"notes": "M=1
|
| 967 |
},
|
| 968 |
"inputs": {
|
| 969 |
"aT": {
|
|
@@ -1000,7 +1121,7 @@
|
|
| 1000 |
"provenance": {
|
| 1001 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1002 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1003 |
-
"notes": "M=1 q8 GEMV with explicit zero points and no bias
|
| 1004 |
},
|
| 1005 |
"inputs": {
|
| 1006 |
"aT": {
|
|
@@ -1032,7 +1153,7 @@
|
|
| 1032 |
"provenance": {
|
| 1033 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1034 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1035 |
-
"notes": "M=1 q8 GEMV with bias and the schema-default zero point
|
| 1036 |
},
|
| 1037 |
"inputs": {
|
| 1038 |
"aT": {
|
|
@@ -1063,7 +1184,7 @@
|
|
| 1063 |
"name": "q8_zero_bias_gemv_m1_tail_n5_ncols8",
|
| 1064 |
"tunables": { "GEMV_N_COLS": 8 },
|
| 1065 |
"provenance": {
|
| 1066 |
-
"notes": "GEMV_N_COLS=8
|
| 1067 |
},
|
| 1068 |
"inputs": {
|
| 1069 |
"aT": {
|
|
@@ -1098,7 +1219,7 @@
|
|
| 1098 |
{
|
| 1099 |
"name": "q8_zero_bias_naive_fallback_tailK_m3_n6",
|
| 1100 |
"provenance": {
|
| 1101 |
-
"notes": "K=17
|
| 1102 |
},
|
| 1103 |
"inputs": {
|
| 1104 |
"aT": {
|
|
@@ -1161,8 +1282,8 @@
|
|
| 1161 |
{
|
| 1162 |
"name": "q4_prefill_tiled_reg_aligned_m32_k32_n4096_at_workgroup_floor",
|
| 1163 |
"provenance": {
|
| 1164 |
-
"source": "
|
| 1165 |
-
"notes": "Aligned
|
| 1166 |
},
|
| 1167 |
"inputs": {
|
| 1168 |
"aT": {
|
|
@@ -1190,7 +1311,7 @@
|
|
| 1190 |
{
|
| 1191 |
"name": "q4_prefill_tiled_reg_tailk_m32_k33_n512_below_wide_n_floor",
|
| 1192 |
"provenance": {
|
| 1193 |
-
"notes": "
|
| 1194 |
},
|
| 1195 |
"inputs": {
|
| 1196 |
"aT": {
|
|
@@ -1273,7 +1394,7 @@
|
|
| 1273 |
"provenance": {
|
| 1274 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1275 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1276 |
-
"notes": "
|
| 1277 |
},
|
| 1278 |
"inputs": {
|
| 1279 |
"aT": {
|
|
@@ -1305,7 +1426,7 @@
|
|
| 1305 |
"provenance": {
|
| 1306 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1307 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1308 |
-
"notes": "
|
| 1309 |
},
|
| 1310 |
"inputs": {
|
| 1311 |
"aT": {
|
|
@@ -1360,7 +1481,7 @@
|
|
| 1360 |
"provenance": {
|
| 1361 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1362 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1363 |
-
"notes": "
|
| 1364 |
},
|
| 1365 |
"attrs": { "K": 128, "N": 64, "bits": 8, "block_size": 32 }
|
| 1366 |
},
|
|
@@ -1392,7 +1513,7 @@
|
|
| 1392 |
"provenance": {
|
| 1393 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1394 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1395 |
-
"notes": "
|
| 1396 |
},
|
| 1397 |
"attrs": { "K": 128, "N": 64, "bits": 8, "block_size": 32 }
|
| 1398 |
},
|
|
@@ -1424,7 +1545,7 @@
|
|
| 1424 |
"provenance": {
|
| 1425 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1426 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1427 |
-
"notes": "
|
| 1428 |
},
|
| 1429 |
"attrs": { "K": 128, "N": 1024, "bits": 4, "block_size": 32 }
|
| 1430 |
},
|
|
@@ -1456,7 +1577,7 @@
|
|
| 1456 |
"provenance": {
|
| 1457 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1458 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1459 |
-
"notes": "
|
| 1460 |
},
|
| 1461 |
"attrs": { "K": 128, "N": 1024, "bits": 4, "block_size": 32 }
|
| 1462 |
},
|
|
@@ -1488,7 +1609,7 @@
|
|
| 1488 |
"provenance": {
|
| 1489 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1490 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1491 |
-
"notes": "
|
| 1492 |
},
|
| 1493 |
"attrs": { "K": 128, "N": 1024, "bits": 8, "block_size": 32 }
|
| 1494 |
},
|
|
@@ -1520,7 +1641,7 @@
|
|
| 1520 |
"provenance": {
|
| 1521 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1522 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1523 |
-
"notes": "
|
| 1524 |
},
|
| 1525 |
"attrs": { "K": 128, "N": 1024, "bits": 8, "block_size": 32 }
|
| 1526 |
},
|
|
@@ -1529,7 +1650,7 @@
|
|
| 1529 |
"provenance": {
|
| 1530 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1531 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1532 |
-
"notes": "
|
| 1533 |
},
|
| 1534 |
"inputs": {
|
| 1535 |
"aT": {
|
|
@@ -1561,7 +1682,7 @@
|
|
| 1561 |
"provenance": {
|
| 1562 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1563 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1564 |
-
"notes": "
|
| 1565 |
},
|
| 1566 |
"inputs": {
|
| 1567 |
"aT": {
|
|
@@ -1593,7 +1714,7 @@
|
|
| 1593 |
"provenance": {
|
| 1594 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1595 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1596 |
-
"notes": "
|
| 1597 |
},
|
| 1598 |
"inputs": {
|
| 1599 |
"aT": {
|
|
@@ -1625,7 +1746,7 @@
|
|
| 1625 |
"provenance": {
|
| 1626 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1627 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1628 |
-
"notes": "
|
| 1629 |
},
|
| 1630 |
"inputs": {
|
| 1631 |
"aT": {
|
|
@@ -1680,7 +1801,7 @@
|
|
| 1680 |
"provenance": {
|
| 1681 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1682 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1683 |
-
"notes": "
|
| 1684 |
},
|
| 1685 |
"attrs": { "K": 128, "N": 64, "bits": 4, "block_size": 32 }
|
| 1686 |
},
|
|
@@ -1712,7 +1833,7 @@
|
|
| 1712 |
"provenance": {
|
| 1713 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1714 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1715 |
-
"notes": "
|
| 1716 |
},
|
| 1717 |
"attrs": { "K": 128, "N": 64, "bits": 4, "block_size": 32 }
|
| 1718 |
},
|
|
@@ -1738,7 +1859,7 @@
|
|
| 1738 |
"provenance": {
|
| 1739 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/dp4a_matmul_nbits.cc",
|
| 1740 |
"test": "accuracy_level=4 int8 activation quantization (DP4AMatMulQuantize + DP4AMatMulNBits)",
|
| 1741 |
-
"notes": "
|
| 1742 |
},
|
| 1743 |
"attrs": { "K": 256, "N": 64, "bits": 4, "block_size": 32, "accuracy_level": 4 }
|
| 1744 |
},
|
|
@@ -1753,7 +1874,7 @@
|
|
| 1753 |
"bT": {
|
| 1754 |
"dtype": "uint8",
|
| 1755 |
"shape": [16, 2, 32],
|
| 1756 |
-
"data": { "kind": "cycle", "values": [
|
| 1757 |
},
|
| 1758 |
"scalesT": { "dtype": "float32", "shape": [16, 2], "data": { "kind": "linspace", "start": 0.02, "end": 0.06 } }
|
| 1759 |
},
|
|
@@ -1761,7 +1882,7 @@
|
|
| 1761 |
"provenance": {
|
| 1762 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/dp4a_matmul_nbits.cc",
|
| 1763 |
"test": "accuracy_level=4 int8 activation quantization (DP4AMatMulQuantize + DP4AMatMulNBits)",
|
| 1764 |
-
"notes": "
|
| 1765 |
},
|
| 1766 |
"attrs": { "K": 128, "N": 16, "bits": 4, "block_size": 64, "accuracy_level": 4 }
|
| 1767 |
},
|
|
@@ -2012,6 +2133,151 @@
|
|
| 2012 |
},
|
| 2013 |
"outputs": { "yT": { "dtype": "float32", "shape": [3, 6], "tolerance": 0.001 } },
|
| 2014 |
"attrs": { "K": 17, "N": 6, "bits": 8, "block_size": 16 }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2015 |
}
|
| 2016 |
]
|
| 2017 |
}
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"fixtureArrays": {
|
| 3 |
"q4_prefill_tiled_reg_tailk_m32_k33_n4096_input_bT": [16, 50, 84, 118, 152, 186, 220, 254, 135, 119, 102, 85, 68, 51, 34, 17],
|
| 4 |
"q4_weight_cycle_b_t": [16, 50, 84, 118, 152, 186, 220, 254, 33, 67, 101, 135, 169, 203, 237, 15],
|
|
|
|
| 77 |
{
|
| 78 |
"name": "q8_zero_bias_prefill_tile4x4_small_m8",
|
| 79 |
"provenance": {
|
| 80 |
+
"notes": "An eight-row q8 prefill with zero points and bias exercises q8 unpacking and the bias epilogue on the small-M tile4x4 route."
|
| 81 |
},
|
| 82 |
"inputs": {
|
| 83 |
"aT": {
|
|
|
|
| 180 |
"provenance": {
|
| 181 |
"source": "onnxruntime/test/contrib_ops/matmul_4bits_test.cc",
|
| 182 |
"test": "MatMulNBits.UnsupportedBlockSize_512",
|
| 183 |
+
"notes": "The schema permits power-of-two block sizes of at least 16, including 512. With A=1, q4 code 9, default zero point 8, and scale 0.25, the result is exactly `512 * (9 - 8) * 0.25 = 128`."
|
| 184 |
},
|
| 185 |
"inputs": {
|
| 186 |
"aT": { "dtype": "float32", "shape": [1, 512], "data": { "kind": "constant", "value": 1.0 } },
|
|
|
|
| 241 |
},
|
| 242 |
{
|
| 243 |
"name": "q4_zero_bias_prefill_sgmat_m32_storage_floor",
|
| 244 |
+
"provenance": {
|
| 245 |
+
"notes": "Explicit zero points and bias exercise the 32-row subgroup-matrix tile at the 16 KiB workgroup-storage floor."
|
| 246 |
+
},
|
| 247 |
"inputs": {
|
| 248 |
"aT": {
|
| 249 |
"dtype": "float32",
|
|
|
|
| 277 |
{
|
| 278 |
"name": "q4_no_zero_prefill_g64_sgmat",
|
| 279 |
"provenance": {
|
| 280 |
+
"notes": "A compact q4 prefill with block size 64 and the default zero point exercises the subgroup-matrix route and its portable tiled fallback."
|
| 281 |
},
|
| 282 |
"inputs": {
|
| 283 |
"aT": {
|
|
|
|
| 324 |
{
|
| 325 |
"name": "q8_no_zero_prefill_sgmat_m32_storage_floor",
|
| 326 |
"provenance": {
|
| 327 |
+
"notes": "A q8 prefill exercises the 32-row subgroup-matrix tile at the 16 KiB workgroup-storage floor."
|
| 328 |
},
|
| 329 |
"inputs": {
|
| 330 |
"aT": {
|
|
|
|
| 378 |
"outputs": { "yT": { "dtype": "float32", "shape": [128, 64], "tolerance": 0.001 } },
|
| 379 |
"attrs": { "K": 128, "N": 64, "bits": 8, "block_size": 32 }
|
| 380 |
},
|
| 381 |
+
{
|
| 382 |
+
"name": "q8_no_zero_prefill_reg_vec4_splitk_m128_k1024_n1024",
|
| 383 |
+
"provenance": {
|
| 384 |
+
"notes": "Without zero points or bias, 32 output tiles over K=1024 use two K slices whose float32 partial sums are combined."
|
| 385 |
+
},
|
| 386 |
+
"inputs": {
|
| 387 |
+
"aT": {
|
| 388 |
+
"dtype": "float32",
|
| 389 |
+
"shape": [128, 1024],
|
| 390 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23, "scale": 0.35 }
|
| 391 |
+
},
|
| 392 |
+
"bT": {
|
| 393 |
+
"dtype": "uint8",
|
| 394 |
+
"shape": [1024, 32, 32],
|
| 395 |
+
"data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/mixed_weight_cycle_b_t" } }
|
| 396 |
+
},
|
| 397 |
+
"scalesT": {
|
| 398 |
+
"dtype": "float32",
|
| 399 |
+
"shape": [1024, 32],
|
| 400 |
+
"data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/quant_scale_cycle_t" } }
|
| 401 |
+
}
|
| 402 |
+
},
|
| 403 |
+
"outputs": { "yT": { "dtype": "float32", "shape": [128, 1024], "tolerance": 0.005 } },
|
| 404 |
+
"attrs": { "K": 1024, "N": 1024, "bits": 8, "block_size": 32 }
|
| 405 |
+
},
|
| 406 |
+
{
|
| 407 |
+
"name": "q8_zero_bias_prefill_reg_vec4_splitk_m128_k1024_n1024",
|
| 408 |
+
"provenance": {
|
| 409 |
+
"notes": "With zero points and bias, the split-K four-wide route applies zero points in each partial pass and adds bias once in the combine."
|
| 410 |
+
},
|
| 411 |
+
"inputs": {
|
| 412 |
+
"aT": {
|
| 413 |
+
"dtype": "float32",
|
| 414 |
+
"shape": [128, 1024],
|
| 415 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23, "scale": 0.35 }
|
| 416 |
+
},
|
| 417 |
+
"bT": {
|
| 418 |
+
"dtype": "uint8",
|
| 419 |
+
"shape": [1024, 32, 32],
|
| 420 |
+
"data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/mixed_weight_cycle_b_t" } }
|
| 421 |
+
},
|
| 422 |
+
"scalesT": {
|
| 423 |
+
"dtype": "float32",
|
| 424 |
+
"shape": [1024, 32],
|
| 425 |
+
"data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/quant_scale_cycle_t" } }
|
| 426 |
+
},
|
| 427 |
+
"zeroPointsT": {
|
| 428 |
+
"dtype": "float32",
|
| 429 |
+
"shape": [1024, 32],
|
| 430 |
+
"data": { "kind": "cycle", "values": [120.0, 132.0, 128.0, 140.0, 135.0, 118.0, 144.0, 126.0] }
|
| 431 |
+
},
|
| 432 |
+
"biasT": {
|
| 433 |
+
"dtype": "float32",
|
| 434 |
+
"shape": [1024],
|
| 435 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41 }
|
| 436 |
+
}
|
| 437 |
+
},
|
| 438 |
+
"outputs": { "yT": { "dtype": "float32", "shape": [128, 1024], "tolerance": 0.005 } },
|
| 439 |
+
"attrs": { "K": 1024, "N": 1024, "bits": 8, "block_size": 32 }
|
| 440 |
+
},
|
| 441 |
+
{
|
| 442 |
+
"name": "q8_zero_only_prefill_reg_vec4_splitk_m128_k1024_n1024",
|
| 443 |
+
"provenance": {
|
| 444 |
+
"notes": "With zero points and no bias, the split-K four-wide route applies zero points in each partial pass and only sums in the combine."
|
| 445 |
+
},
|
| 446 |
+
"inputs": {
|
| 447 |
+
"aT": {
|
| 448 |
+
"dtype": "float32",
|
| 449 |
+
"shape": [128, 1024],
|
| 450 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23, "scale": 0.35 }
|
| 451 |
+
},
|
| 452 |
+
"bT": {
|
| 453 |
+
"dtype": "uint8",
|
| 454 |
+
"shape": [1024, 32, 32],
|
| 455 |
+
"data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/mixed_weight_cycle_b_t" } }
|
| 456 |
+
},
|
| 457 |
+
"scalesT": {
|
| 458 |
+
"dtype": "float32",
|
| 459 |
+
"shape": [1024, 32],
|
| 460 |
+
"data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/quant_scale_cycle_t" } }
|
| 461 |
+
},
|
| 462 |
+
"zeroPointsT": {
|
| 463 |
+
"dtype": "float32",
|
| 464 |
+
"shape": [1024, 32],
|
| 465 |
+
"data": { "kind": "cycle", "values": [120.0, 132.0, 128.0, 140.0, 135.0, 118.0, 144.0, 126.0] }
|
| 466 |
+
}
|
| 467 |
+
},
|
| 468 |
+
"outputs": { "yT": { "dtype": "float32", "shape": [128, 1024], "tolerance": 0.005 } },
|
| 469 |
+
"attrs": { "K": 1024, "N": 1024, "bits": 8, "block_size": 32 }
|
| 470 |
+
},
|
| 471 |
+
{
|
| 472 |
+
"name": "q8_bias_only_prefill_reg_vec4_splitk_m128_k1024_n1024",
|
| 473 |
+
"provenance": {
|
| 474 |
+
"notes": "With bias and the implicit zero point, the split-K four-wide route adds bias once after combining the partial sums."
|
| 475 |
+
},
|
| 476 |
+
"inputs": {
|
| 477 |
+
"aT": {
|
| 478 |
+
"dtype": "float32",
|
| 479 |
+
"shape": [128, 1024],
|
| 480 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23, "scale": 0.35 }
|
| 481 |
+
},
|
| 482 |
+
"bT": {
|
| 483 |
+
"dtype": "uint8",
|
| 484 |
+
"shape": [1024, 32, 32],
|
| 485 |
+
"data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/mixed_weight_cycle_b_t" } }
|
| 486 |
+
},
|
| 487 |
+
"scalesT": {
|
| 488 |
+
"dtype": "float32",
|
| 489 |
+
"shape": [1024, 32],
|
| 490 |
+
"data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/quant_scale_cycle_t" } }
|
| 491 |
+
},
|
| 492 |
+
"biasT": {
|
| 493 |
+
"dtype": "float32",
|
| 494 |
+
"shape": [1024],
|
| 495 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41 }
|
| 496 |
+
}
|
| 497 |
+
},
|
| 498 |
+
"outputs": { "yT": { "dtype": "float32", "shape": [128, 1024], "tolerance": 0.005 } },
|
| 499 |
+
"attrs": { "K": 1024, "N": 1024, "bits": 8, "block_size": 32 }
|
| 500 |
+
},
|
| 501 |
{
|
| 502 |
"name": "q8_zero_bias_prefill_sgmat_m32_storage_floor",
|
| 503 |
+
"provenance": {
|
| 504 |
+
"notes": "Q8 weights with explicit zero points and bias exercise the 32-row subgroup-matrix tile at the 16 KiB storage floor."
|
| 505 |
+
},
|
| 506 |
"inputs": {
|
| 507 |
"aT": {
|
| 508 |
"dtype": "float32",
|
|
|
|
| 815 |
}
|
| 816 |
},
|
| 817 |
"outputs": { "yT": { "dtype": "float16", "shape": [512, 1024], "tolerance": 0.0001, "relTolerance": 0.001 } },
|
| 818 |
+
"provenance": { "notes": "Float16 activations exercise float16 A/B staging on the register-tiled prefill path." },
|
|
|
|
|
|
|
| 819 |
"attrs": { "K": 128, "N": 1024, "bits": 4, "block_size": 32 }
|
| 820 |
},
|
| 821 |
{
|
|
|
|
| 893 |
},
|
| 894 |
"outputs": { "yT": { "dtype": "float16", "shape": [512, 1024], "tolerance": 0.0001, "relTolerance": 0.001 } },
|
| 895 |
"provenance": {
|
| 896 |
+
"notes": "Eight-bit weights give the widest dequantized range, exercising float16 staging and store rounding on the register-tiled prefill path."
|
| 897 |
},
|
| 898 |
"attrs": { "K": 128, "N": 1024, "bits": 8, "block_size": 32 }
|
| 899 |
},
|
|
|
|
| 957 |
{
|
| 958 |
"name": "q4_gemv_default_zero_m1_tail_n7",
|
| 959 |
"provenance": {
|
| 960 |
+
"notes": "M=1 and N=7 leave three live columns in the final four-column output group. The guarded stores must write those columns without accessing the eighth column; zero points are omitted."
|
| 961 |
},
|
| 962 |
"inputs": {
|
| 963 |
"aT": {
|
|
|
|
| 982 |
{
|
| 983 |
"name": "q4_gemv_default_zero_m1_odd_k33_n5",
|
| 984 |
"provenance": {
|
| 985 |
+
"notes": "With K=33, the last stored byte of the second q4 block contains one live nibble and one beyond K. The GEMV must guard the out-of-range activation."
|
| 986 |
},
|
| 987 |
"inputs": {
|
| 988 |
"aT": {
|
|
|
|
| 1007 |
{
|
| 1008 |
"name": "q2_gemv_default_zero_m1_k18_n5",
|
| 1009 |
"provenance": {
|
| 1010 |
+
"notes": "Four q2 codes share each byte, and K=18 leaves two codes in the final byte beyond K. The GEMV must unpack the live codes and guard both out-of-range activations."
|
| 1011 |
},
|
| 1012 |
"inputs": {
|
| 1013 |
"aT": {
|
|
|
|
| 1059 |
"name": "q4_gemv_default_zero_m1_tail_n7_ncols8",
|
| 1060 |
"tunables": { "GEMV_N_COLS": 8 },
|
| 1061 |
"provenance": {
|
| 1062 |
+
"notes": "With `GEMV_N_COLS = 8` and N=7, one workgroup has active columns 4 through 6 and a fully guarded column 7 in its second group."
|
| 1063 |
},
|
| 1064 |
"inputs": {
|
| 1065 |
"aT": {
|
|
|
|
| 1084 |
{
|
| 1085 |
"name": "q8_zero_bias_gemv_m1_tail_n5",
|
| 1086 |
"provenance": {
|
| 1087 |
+
"notes": "M=1 and N=5 leave one live column in the final four-column output group. The case combines guarded tail stores with q8 unpacking, explicit zero points, and the bias epilogue."
|
| 1088 |
},
|
| 1089 |
"inputs": {
|
| 1090 |
"aT": {
|
|
|
|
| 1121 |
"provenance": {
|
| 1122 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1123 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1124 |
+
"notes": "An M=1 q8 GEMV with explicit zero points and no bias isolates the zero-point binding layout and epilogue."
|
| 1125 |
},
|
| 1126 |
"inputs": {
|
| 1127 |
"aT": {
|
|
|
|
| 1153 |
"provenance": {
|
| 1154 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1155 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1156 |
+
"notes": "An M=1 q8 GEMV with bias and the schema-default zero point isolates the bias binding layout and epilogue."
|
| 1157 |
},
|
| 1158 |
"inputs": {
|
| 1159 |
"aT": {
|
|
|
|
| 1184 |
"name": "q8_zero_bias_gemv_m1_tail_n5_ncols8",
|
| 1185 |
"tunables": { "GEMV_N_COLS": 8 },
|
| 1186 |
"provenance": {
|
| 1187 |
+
"notes": "With `GEMV_N_COLS = 8` and N=5, q8 unpacking, explicit zero points, and bias run with one live column in the second group."
|
| 1188 |
},
|
| 1189 |
"inputs": {
|
| 1190 |
"aT": {
|
|
|
|
| 1219 |
{
|
| 1220 |
"name": "q8_zero_bias_naive_fallback_tailK_m3_n6",
|
| 1221 |
"provenance": {
|
| 1222 |
+
"notes": "K=17 with block size 16 leaves a partial block; M=3 bypasses GEMV and N=6 remains below the tiled floors. The scalar fallback handles q8 unpacking, per-block zero points, bias, and the K tail."
|
| 1223 |
},
|
| 1224 |
"inputs": {
|
| 1225 |
"aT": {
|
|
|
|
| 1282 |
{
|
| 1283 |
"name": "q4_prefill_tiled_reg_aligned_m32_k32_n4096_at_workgroup_floor",
|
| 1284 |
"provenance": {
|
| 1285 |
+
"source": "synthetic",
|
| 1286 |
+
"notes": "Aligned K=32, M=32, and N=4096 produce exactly 64 workgroups, exercising the register-tiled prefill route at its workgroup floor."
|
| 1287 |
},
|
| 1288 |
"inputs": {
|
| 1289 |
"aT": {
|
|
|
|
| 1311 |
{
|
| 1312 |
"name": "q4_prefill_tiled_reg_tailk_m32_k33_n512_below_wide_n_floor",
|
| 1313 |
"provenance": {
|
| 1314 |
+
"notes": "K=33 exercises a padded final block while N=512 places the register-tiled prefill route in its relaxed narrow-output band."
|
| 1315 |
},
|
| 1316 |
"inputs": {
|
| 1317 |
"aT": {
|
|
|
|
| 1394 |
"provenance": {
|
| 1395 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1396 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1397 |
+
"notes": "The only optional input is zeroPointsT; the expected output isolates explicit zero-point decoding from bias handling."
|
| 1398 |
},
|
| 1399 |
"inputs": {
|
| 1400 |
"aT": {
|
|
|
|
| 1426 |
"provenance": {
|
| 1427 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1428 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1429 |
+
"notes": "The only optional input is biasT; zero-point decoding uses the schema default, isolating the bias epilogue."
|
| 1430 |
},
|
| 1431 |
"inputs": {
|
| 1432 |
"aT": {
|
|
|
|
| 1481 |
"provenance": {
|
| 1482 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1483 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1484 |
+
"notes": "The only optional input is zeroPointsT; the expected output isolates explicit zero-point decoding from bias handling."
|
| 1485 |
},
|
| 1486 |
"attrs": { "K": 128, "N": 64, "bits": 8, "block_size": 32 }
|
| 1487 |
},
|
|
|
|
| 1513 |
"provenance": {
|
| 1514 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1515 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1516 |
+
"notes": "The only optional input is biasT; zero-point decoding uses the schema default, isolating the bias epilogue."
|
| 1517 |
},
|
| 1518 |
"attrs": { "K": 128, "N": 64, "bits": 8, "block_size": 32 }
|
| 1519 |
},
|
|
|
|
| 1545 |
"provenance": {
|
| 1546 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1547 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1548 |
+
"notes": "The only optional input is zeroPointsT; the expected output isolates explicit zero-point decoding from bias handling."
|
| 1549 |
},
|
| 1550 |
"attrs": { "K": 128, "N": 1024, "bits": 4, "block_size": 32 }
|
| 1551 |
},
|
|
|
|
| 1577 |
"provenance": {
|
| 1578 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1579 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1580 |
+
"notes": "The only optional input is biasT; zero-point decoding uses the schema default, isolating the bias epilogue."
|
| 1581 |
},
|
| 1582 |
"attrs": { "K": 128, "N": 1024, "bits": 4, "block_size": 32 }
|
| 1583 |
},
|
|
|
|
| 1609 |
"provenance": {
|
| 1610 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1611 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1612 |
+
"notes": "The only optional input is zeroPointsT; the expected output isolates explicit zero-point decoding from bias handling."
|
| 1613 |
},
|
| 1614 |
"attrs": { "K": 128, "N": 1024, "bits": 8, "block_size": 32 }
|
| 1615 |
},
|
|
|
|
| 1641 |
"provenance": {
|
| 1642 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1643 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1644 |
+
"notes": "The only optional input is biasT; zero-point decoding uses the schema default, isolating the bias epilogue."
|
| 1645 |
},
|
| 1646 |
"attrs": { "K": 128, "N": 1024, "bits": 8, "block_size": 32 }
|
| 1647 |
},
|
|
|
|
| 1650 |
"provenance": {
|
| 1651 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1652 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1653 |
+
"notes": "The only optional input is zeroPointsT; the expected output isolates explicit zero-point decoding from bias handling."
|
| 1654 |
},
|
| 1655 |
"inputs": {
|
| 1656 |
"aT": {
|
|
|
|
| 1682 |
"provenance": {
|
| 1683 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1684 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1685 |
+
"notes": "The only optional input is biasT; zero-point decoding uses the schema default, isolating the bias epilogue."
|
| 1686 |
},
|
| 1687 |
"inputs": {
|
| 1688 |
"aT": {
|
|
|
|
| 1714 |
"provenance": {
|
| 1715 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1716 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1717 |
+
"notes": "The only optional input is zeroPointsT; the expected output isolates explicit zero-point decoding from bias handling."
|
| 1718 |
},
|
| 1719 |
"inputs": {
|
| 1720 |
"aT": {
|
|
|
|
| 1746 |
"provenance": {
|
| 1747 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1748 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1749 |
+
"notes": "The only optional input is biasT; zero-point decoding uses the schema default, isolating the bias epilogue."
|
| 1750 |
},
|
| 1751 |
"inputs": {
|
| 1752 |
"aT": {
|
|
|
|
| 1801 |
"provenance": {
|
| 1802 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1803 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1804 |
+
"notes": "The only optional input is zeroPointsT; the expected output isolates explicit zero-point decoding from bias handling."
|
| 1805 |
},
|
| 1806 |
"attrs": { "K": 128, "N": 64, "bits": 4, "block_size": 32 }
|
| 1807 |
},
|
|
|
|
| 1833 |
"provenance": {
|
| 1834 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/matmul_nbits.cc",
|
| 1835 |
"test": "independent has_zero_points/has_bias epilogues",
|
| 1836 |
+
"notes": "The only optional input is biasT; zero-point decoding uses the schema default, isolating the bias epilogue."
|
| 1837 |
},
|
| 1838 |
"attrs": { "K": 128, "N": 64, "bits": 4, "block_size": 32 }
|
| 1839 |
},
|
|
|
|
| 1859 |
"provenance": {
|
| 1860 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/dp4a_matmul_nbits.cc",
|
| 1861 |
"test": "accuracy_level=4 int8 activation quantization (DP4AMatMulQuantize + DP4AMatMulNBits)",
|
| 1862 |
+
"notes": "Accuracy level 4 quantizes A per 128-element block using `max_abs / 127` and signed-normalized rounding, then multiplies q4 weights with packed integer dots. Tolerances account for the permitted activation-quantization error."
|
| 1863 |
},
|
| 1864 |
"attrs": { "K": 256, "N": 64, "bits": 4, "block_size": 32, "accuracy_level": 4 }
|
| 1865 |
},
|
|
|
|
| 1874 |
"bT": {
|
| 1875 |
"dtype": "uint8",
|
| 1876 |
"shape": [16, 2, 32],
|
| 1877 |
+
"data": { "kind": "cycle", "values": [171, 205, 46, 251, 218, 201, 31, 186, 237, 220, 154] }
|
| 1878 |
},
|
| 1879 |
"scalesT": { "dtype": "float32", "shape": [16, 2], "data": { "kind": "linspace", "start": 0.02, "end": 0.06 } }
|
| 1880 |
},
|
|
|
|
| 1882 |
"provenance": {
|
| 1883 |
"source": "onnxruntime/contrib_ops/webgpu/quantization/dp4a_matmul_nbits.cc",
|
| 1884 |
"test": "accuracy_level=4 int8 activation quantization (DP4AMatMulQuantize + DP4AMatMulNBits)",
|
| 1885 |
+
"notes": "Diverges from the upstream test's inputs (inputs.bT cycle [37, 201, 116, 88, 250, 13, 164, 95, 142, 7, 233] -> cycle [171, 205, 46, 251, 218, 201, 31, 186, 237, 220, 154]); the expected output is recomputed by the CPU reference for the new inputs. Accuracy level 4 quantizes A per 128-element block using `max_abs / 127` and signed-normalized rounding, then multiplies q4 weights with packed integer dots. Tolerances account for the permitted activation-quantization error, so the weight codes sit mostly above the default zero point: a near-zero-mean code cycle cancels the product down to the magnitude of that error, leaving no tolerance that can see a scale mistake."
|
| 1886 |
},
|
| 1887 |
"attrs": { "K": 128, "N": 16, "bits": 4, "block_size": 64, "accuracy_level": 4 }
|
| 1888 |
},
|
|
|
|
| 2133 |
},
|
| 2134 |
"outputs": { "yT": { "dtype": "float32", "shape": [3, 6], "tolerance": 0.001 } },
|
| 2135 |
"attrs": { "K": 17, "N": 6, "bits": 8, "block_size": 16 }
|
| 2136 |
+
},
|
| 2137 |
+
{
|
| 2138 |
+
"name": "q4_prefill_tile4x4_m4_k128_n4100_column_groups_partial_group",
|
| 2139 |
+
"provenance": {
|
| 2140 |
+
"notes": "K=128 at block 32 is sixteen packed words per column, so the small-M kernel runs 16 K-lanes and four column groups (16 columns) per workgroup. N=4100 is 256 full workgroups plus one whose second group starts at column 4096 and whose third and fourth groups lie past N, so the group-valid guard and the partial last group are both exercised at scale; M=4 is one row tile."
|
| 2141 |
+
},
|
| 2142 |
+
"inputs": {
|
| 2143 |
+
"aT": {
|
| 2144 |
+
"dtype": "float32",
|
| 2145 |
+
"shape": [4, 128],
|
| 2146 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23, "scale": 0.35 }
|
| 2147 |
+
},
|
| 2148 |
+
"bT": {
|
| 2149 |
+
"dtype": "uint8",
|
| 2150 |
+
"shape": [4100, 4, 16],
|
| 2151 |
+
"data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/q4_weight_cycle_b_t" } }
|
| 2152 |
+
},
|
| 2153 |
+
"scalesT": {
|
| 2154 |
+
"dtype": "float32",
|
| 2155 |
+
"shape": [4100, 4],
|
| 2156 |
+
"data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/quant_scale_cycle_t" } }
|
| 2157 |
+
}
|
| 2158 |
+
},
|
| 2159 |
+
"outputs": { "yT": { "dtype": "float32", "shape": [4, 4100], "tolerance": 0.0001 } },
|
| 2160 |
+
"attrs": { "K": 128, "N": 4100, "bits": 4, "block_size": 32 }
|
| 2161 |
+
},
|
| 2162 |
+
{
|
| 2163 |
+
"name": "q8_zero_bias_prefill_tile4x4_m5_k64_n300_column_groups",
|
| 2164 |
+
"provenance": {
|
| 2165 |
+
"notes": "q8 at K=64, block 32: sixteen packed words per column, four 8-bit codes per word, so a word carries four K-adjacent codes of one block. N=300 with sixteen columns per workgroup leaves a final workgroup whose last group holds columns 296..299 and M=5 leaves a second row tile with one live row, so the per-row and per-column guards run together with zero points and bias."
|
| 2166 |
+
},
|
| 2167 |
+
"inputs": {
|
| 2168 |
+
"aT": {
|
| 2169 |
+
"dtype": "float32",
|
| 2170 |
+
"shape": [5, 64],
|
| 2171 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.09, "cosStep": 0.27, "scale": 0.4 }
|
| 2172 |
+
},
|
| 2173 |
+
"bT": {
|
| 2174 |
+
"dtype": "uint8",
|
| 2175 |
+
"shape": [300, 2, 32],
|
| 2176 |
+
"data": { "kind": "cycle", "values": [17, 200, 91, 45, 233, 128, 7, 176, 250, 33, 142, 99, 210, 64, 188] }
|
| 2177 |
+
},
|
| 2178 |
+
"scalesT": {
|
| 2179 |
+
"dtype": "float32",
|
| 2180 |
+
"shape": [300, 2],
|
| 2181 |
+
"data": { "kind": "cycle", "values": [0.04, 0.055, 0.05, 0.065, 0.06, 0.075, 0.07] }
|
| 2182 |
+
},
|
| 2183 |
+
"zeroPointsT": {
|
| 2184 |
+
"dtype": "float32",
|
| 2185 |
+
"shape": [300, 2],
|
| 2186 |
+
"data": { "kind": "cycle", "values": [120.0, 132.0, 128.0, 140.0, 135.0, 118.0, 144.0] }
|
| 2187 |
+
},
|
| 2188 |
+
"biasT": {
|
| 2189 |
+
"dtype": "float32",
|
| 2190 |
+
"shape": [300],
|
| 2191 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "scale": 0.1 }
|
| 2192 |
+
}
|
| 2193 |
+
},
|
| 2194 |
+
"outputs": { "yT": { "dtype": "float32", "shape": [5, 300], "tolerance": 0.001 } },
|
| 2195 |
+
"attrs": { "K": 64, "N": 300, "bits": 8, "block_size": 32 }
|
| 2196 |
+
},
|
| 2197 |
+
{
|
| 2198 |
+
"name": "q4_gemv_default_zero_m1_k2080_n6_two_vector_trips",
|
| 2199 |
+
"provenance": {
|
| 2200 |
+
"notes": "K=2080 at block size 32 yields 65 sixteen-byte vectors per column, so lane 0 makes a second vector-loop trip and applies the 65th block's scale. N=6 leaves the second column group half empty."
|
| 2201 |
+
},
|
| 2202 |
+
"inputs": {
|
| 2203 |
+
"aT": {
|
| 2204 |
+
"dtype": "float32",
|
| 2205 |
+
"shape": [1, 2080],
|
| 2206 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.031 }
|
| 2207 |
+
},
|
| 2208 |
+
"bT": {
|
| 2209 |
+
"dtype": "uint8",
|
| 2210 |
+
"shape": [6, 65, 16],
|
| 2211 |
+
"data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/q4_weight_cycle_b_t" } }
|
| 2212 |
+
},
|
| 2213 |
+
"scalesT": {
|
| 2214 |
+
"dtype": "float32",
|
| 2215 |
+
"shape": [6, 65],
|
| 2216 |
+
"data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/quant_scale_cycle_t" } }
|
| 2217 |
+
}
|
| 2218 |
+
},
|
| 2219 |
+
"outputs": { "yT": { "dtype": "float32", "shape": [1, 6], "tolerance": 0.0001 } },
|
| 2220 |
+
"attrs": { "K": 2080, "N": 6, "bits": 4, "block_size": 32 }
|
| 2221 |
+
},
|
| 2222 |
+
{
|
| 2223 |
+
"name": "q8_gemv_zero_bias_m1_k48_block16_word_vectors",
|
| 2224 |
+
"provenance": {
|
| 2225 |
+
"notes": "q8 at block 16 is a sixteen-byte blob, so the GEMV still loads whole sixteen-byte vectors (one per block) while q4 at block 16 would fall to single words; K=48 is three vectors and N=9 leaves a partial column group, with zero points and bias."
|
| 2226 |
+
},
|
| 2227 |
+
"inputs": {
|
| 2228 |
+
"aT": {
|
| 2229 |
+
"dtype": "float32",
|
| 2230 |
+
"shape": [1, 48],
|
| 2231 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29 }
|
| 2232 |
+
},
|
| 2233 |
+
"bT": {
|
| 2234 |
+
"dtype": "uint8",
|
| 2235 |
+
"shape": [9, 3, 16],
|
| 2236 |
+
"data": { "kind": "cycle", "values": [17, 200, 91, 45, 233, 128, 7, 176, 250, 33, 142, 99, 210, 64, 188] }
|
| 2237 |
+
},
|
| 2238 |
+
"scalesT": {
|
| 2239 |
+
"dtype": "float32",
|
| 2240 |
+
"shape": [9, 3],
|
| 2241 |
+
"data": { "kind": "cycle", "values": [0.04, 0.055, 0.05, 0.065, 0.06, 0.075, 0.07] }
|
| 2242 |
+
},
|
| 2243 |
+
"zeroPointsT": {
|
| 2244 |
+
"dtype": "float32",
|
| 2245 |
+
"shape": [9, 3],
|
| 2246 |
+
"data": { "kind": "cycle", "values": [120.0, 132.0, 128.0, 140.0, 135.0, 118.0, 144.0] }
|
| 2247 |
+
},
|
| 2248 |
+
"biasT": {
|
| 2249 |
+
"dtype": "float32",
|
| 2250 |
+
"shape": [9],
|
| 2251 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.41, "scale": 0.1 }
|
| 2252 |
+
}
|
| 2253 |
+
},
|
| 2254 |
+
"outputs": { "yT": { "dtype": "float32", "shape": [1, 9], "tolerance": 0.0001 } },
|
| 2255 |
+
"attrs": { "K": 48, "N": 9, "bits": 8, "block_size": 16 }
|
| 2256 |
+
},
|
| 2257 |
+
{
|
| 2258 |
+
"name": "q4_gemv_default_zero_m1_block16_single_word_vectors_k40",
|
| 2259 |
+
"provenance": {
|
| 2260 |
+
"notes": "q4 at block 16 is an eight-byte blob, below the sixteen-byte vector, so the GEMV falls to one-word vectors (eight codes each) and K=40 ends half way through the third block: the last vector's trailing codes lie past K and must contribute zero."
|
| 2261 |
+
},
|
| 2262 |
+
"inputs": {
|
| 2263 |
+
"aT": {
|
| 2264 |
+
"dtype": "float32",
|
| 2265 |
+
"shape": [1, 40],
|
| 2266 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.37 }
|
| 2267 |
+
},
|
| 2268 |
+
"bT": {
|
| 2269 |
+
"dtype": "uint8",
|
| 2270 |
+
"shape": [5, 3, 8],
|
| 2271 |
+
"data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/q4_weight_cycle_b_t" } }
|
| 2272 |
+
},
|
| 2273 |
+
"scalesT": {
|
| 2274 |
+
"dtype": "float32",
|
| 2275 |
+
"shape": [5, 3],
|
| 2276 |
+
"data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/quant_scale_cycle_t" } }
|
| 2277 |
+
}
|
| 2278 |
+
},
|
| 2279 |
+
"outputs": { "yT": { "dtype": "float32", "shape": [1, 5], "tolerance": 0.0001 } },
|
| 2280 |
+
"attrs": { "K": 40, "N": 5, "bits": 4, "block_size": 16 }
|
| 2281 |
}
|
| 2282 |
]
|
| 2283 |
}
|