Xenova HF Staff commited on
Commit
19bef0f
·
verified ·
1 Parent(s): ef3074d

sync c928d21e6cc1

Browse files
build/webgpu/manifest.json CHANGED
@@ -640,7 +640,11 @@
640
  },
641
  "derive": {
642
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
 
643
  "narrowSubgroupRange": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize < device.adapterInfo.subgroupMaxSize and device.adapterInfo.subgroupMaxSize <= 16",
 
 
 
644
  "packedFeature": "device.wgslLanguageFeatures.has(\"packed_4x8_integer_dot_product\")",
645
  "kBlocksExpected": "ceilDiv(attrs.K, attrs.block_size)",
646
  "blobSizeExpected": "ceilDiv(attrs.block_size * attrs.bits, 8)",
@@ -760,10 +764,9 @@
760
  "requires": {
761
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
762
  "limits": { "maxComputeWorkgroupStorageSize": 16384 },
763
- "subgroupMinSize": 32,
764
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
765
  },
766
- "when": ["commonShapeValid", "defaultEpilogue", "bitsSupported", "attrs.K % attrs.block_size == 0", "attrs.block_size % 16 == 0", "attrs.K % 32 == 0", "aRows >= 32", "has(device.adapterInfo, \"subgroupMinSize\")", "has(device.adapterInfo, \"subgroupMaxSize\")", "device.adapterInfo.subgroupMinSize == 32", "device.adapterInfo.subgroupMaxSize == 32", "dispatchN64 <= device.limits.maxComputeWorkgroupsPerDimension", "sgmatDispatchM <= device.limits.maxComputeWorkgroupsPerDimension", "portableWorkgroupFits", "sgmatWorkgroupFits"],
767
  "constants": {
768
  "hasZero": false,
769
  "hasBias": false,
@@ -947,10 +950,9 @@
947
  "requires": {
948
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
949
  "limits": { "maxComputeWorkgroupStorageSize": 16384 },
950
- "subgroupMinSize": 32,
951
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
952
  },
953
- "when": ["commonShapeValid", "zeroBiasEpilogue", "bitsSupported", "attrs.K % attrs.block_size == 0", "attrs.block_size % 16 == 0", "attrs.K % 32 == 0", "aRows >= 32", "has(device.adapterInfo, \"subgroupMinSize\")", "has(device.adapterInfo, \"subgroupMaxSize\")", "device.adapterInfo.subgroupMinSize == 32", "device.adapterInfo.subgroupMaxSize == 32", "dispatchN64 <= device.limits.maxComputeWorkgroupsPerDimension", "sgmatDispatchM <= device.limits.maxComputeWorkgroupsPerDimension", "portableWorkgroupFits", "sgmatWorkgroupFits"],
954
  "constants": {
955
  "hasZero": true,
956
  "hasBias": true,
@@ -1134,10 +1136,9 @@
1134
  "requires": {
1135
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
1136
  "limits": { "maxComputeWorkgroupStorageSize": 16384 },
1137
- "subgroupMinSize": 32,
1138
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
1139
  },
1140
- "when": ["commonShapeValid", "zeroOnlyEpilogue", "bitsSupported", "attrs.K % attrs.block_size == 0", "attrs.block_size % 16 == 0", "attrs.K % 32 == 0", "aRows >= 32", "has(device.adapterInfo, \"subgroupMinSize\")", "has(device.adapterInfo, \"subgroupMaxSize\")", "device.adapterInfo.subgroupMinSize == 32", "device.adapterInfo.subgroupMaxSize == 32", "dispatchN64 <= device.limits.maxComputeWorkgroupsPerDimension", "sgmatDispatchM <= device.limits.maxComputeWorkgroupsPerDimension", "portableWorkgroupFits", "sgmatWorkgroupFits"],
1141
  "constants": {
1142
  "hasZero": true,
1143
  "hasBias": false,
@@ -1321,10 +1322,9 @@
1321
  "requires": {
1322
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
1323
  "limits": { "maxComputeWorkgroupStorageSize": 16384 },
1324
- "subgroupMinSize": 32,
1325
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
1326
  },
1327
- "when": ["commonShapeValid", "biasOnlyEpilogue", "bitsSupported", "attrs.K % attrs.block_size == 0", "attrs.block_size % 16 == 0", "attrs.K % 32 == 0", "aRows >= 32", "has(device.adapterInfo, \"subgroupMinSize\")", "has(device.adapterInfo, \"subgroupMaxSize\")", "device.adapterInfo.subgroupMinSize == 32", "device.adapterInfo.subgroupMaxSize == 32", "dispatchN64 <= device.limits.maxComputeWorkgroupsPerDimension", "sgmatDispatchM <= device.limits.maxComputeWorkgroupsPerDimension", "portableWorkgroupFits", "sgmatWorkgroupFits"],
1328
  "constants": {
1329
  "hasZero": false,
1330
  "hasBias": true,
 
640
  },
641
  "derive": {
642
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
643
+ "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
644
  "narrowSubgroupRange": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize < device.adapterInfo.subgroupMaxSize and device.adapterInfo.subgroupMaxSize <= 16",
645
+ "canPinSubgroupSize32": "device.features.has(\"subgroups\") and device.features.has(\"subgroup-size-control\") and has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize <= 32 and device.adapterInfo.subgroupMaxSize >= 32",
646
+ "pinSubgroupSize32": "canPinSubgroupSize32 and not wave32Adapter",
647
+ "wave32Effective": "wave32Adapter or pinSubgroupSize32",
648
  "packedFeature": "device.wgslLanguageFeatures.has(\"packed_4x8_integer_dot_product\")",
649
  "kBlocksExpected": "ceilDiv(attrs.K, attrs.block_size)",
650
  "blobSizeExpected": "ceilDiv(attrs.block_size * attrs.bits, 8)",
 
764
  "requires": {
765
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
766
  "limits": { "maxComputeWorkgroupStorageSize": 16384 },
 
767
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
768
  },
769
+ "when": ["commonShapeValid", "defaultEpilogue", "bitsSupported", "attrs.K % attrs.block_size == 0", "attrs.block_size % 16 == 0", "attrs.K % 32 == 0", "aRows >= 32", "wave32Effective", "dispatchN64 <= device.limits.maxComputeWorkgroupsPerDimension", "sgmatDispatchM <= device.limits.maxComputeWorkgroupsPerDimension", "portableWorkgroupFits", "sgmatWorkgroupFits"],
770
  "constants": {
771
  "hasZero": false,
772
  "hasBias": false,
 
950
  "requires": {
951
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
952
  "limits": { "maxComputeWorkgroupStorageSize": 16384 },
 
953
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
954
  },
955
+ "when": ["commonShapeValid", "zeroBiasEpilogue", "bitsSupported", "attrs.K % attrs.block_size == 0", "attrs.block_size % 16 == 0", "attrs.K % 32 == 0", "aRows >= 32", "wave32Effective", "dispatchN64 <= device.limits.maxComputeWorkgroupsPerDimension", "sgmatDispatchM <= device.limits.maxComputeWorkgroupsPerDimension", "portableWorkgroupFits", "sgmatWorkgroupFits"],
956
  "constants": {
957
  "hasZero": true,
958
  "hasBias": true,
 
1136
  "requires": {
1137
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
1138
  "limits": { "maxComputeWorkgroupStorageSize": 16384 },
 
1139
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
1140
  },
1141
+ "when": ["commonShapeValid", "zeroOnlyEpilogue", "bitsSupported", "attrs.K % attrs.block_size == 0", "attrs.block_size % 16 == 0", "attrs.K % 32 == 0", "aRows >= 32", "wave32Effective", "dispatchN64 <= device.limits.maxComputeWorkgroupsPerDimension", "sgmatDispatchM <= device.limits.maxComputeWorkgroupsPerDimension", "portableWorkgroupFits", "sgmatWorkgroupFits"],
1142
  "constants": {
1143
  "hasZero": true,
1144
  "hasBias": false,
 
1322
  "requires": {
1323
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
1324
  "limits": { "maxComputeWorkgroupStorageSize": 16384 },
 
1325
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
1326
  },
1327
+ "when": ["commonShapeValid", "biasOnlyEpilogue", "bitsSupported", "attrs.K % attrs.block_size == 0", "attrs.block_size % 16 == 0", "attrs.K % 32 == 0", "aRows >= 32", "wave32Effective", "dispatchN64 <= device.limits.maxComputeWorkgroupsPerDimension", "sgmatDispatchM <= device.limits.maxComputeWorkgroupsPerDimension", "portableWorkgroupFits", "sgmatWorkgroupFits"],
1328
  "constants": {
1329
  "hasZero": false,
1330
  "hasBias": true,
build/webgpu/matmul-nbits-gemv-q4.wgsl.jinja CHANGED
@@ -8,9 +8,12 @@ enable f16;
8
  {% set nCols = gemvNCols %}
9
  {% set groups = 2 if nCols == 8 else 1 %}
10
  {% set comps = ["x", "y", "z", "w"] %}
 
 
11
 
12
  const WG: u32 = {{ workgroupSize }}u;
13
  const N_COLS: u32 = {{ nCols }}u;
 
14
 
15
  {% for g in range(groups) %}
16
  {% set sfx = "" if groups == 1 else g %}
@@ -18,25 +21,11 @@ var<workgroup> partials{{ sfx }}: array<vec4<f32>, {{ workgroupSize }}>;
18
  {% endfor %}
19
 
20
  // {{ bits }}-bit unpack: q2 packs four codes per byte, q4 two nibbles, q8 one byte/weight.
21
- {% macro matmul_nbits_packed_code(fn="packed_weight", buffer="b", kBlocks="params.kBlocks", blobSize="params.blobSize", bits=4) %}
22
- fn {{ fn }}(n: u32, block: u32, offset: u32) -> u32 {
23
- {% if bits == 2 %}
24
- let byte_index = offset / 4u;
25
- let shift = (offset % 4u) * 2u;
26
- let packed_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + byte_index;
27
- return ({{ buffer }}[packed_index] >> shift) & 3u;
28
- {% elif bits == 4 %}
29
- let byte_index = offset / 2u;
30
- let shift = (offset % 2u) * 4u;
31
- let packed_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + byte_index;
32
- return ({{ buffer }}[packed_index] >> shift) & 15u;
33
- {% else %}
34
- let packed_index = (n * {{ kBlocks }} + block) * {{ blobSize }} + offset;
35
- return {{ buffer }}[packed_index] & 255u;
36
- {% endif %}
37
  }
38
- {%- endmacro %}
39
- {{- matmul_nbits_packed_code(bits=bits) }}
40
 
41
  // Per-(column, block) zero point. Symmetric weights use the baked default
42
  // ({{ defaultZero }}); asymmetric weights carry one widened u32 per (col, block)
@@ -68,23 +57,41 @@ fn main(
68
  var acc{{ sfx }} = vec4<f32>(0.0);
69
  {% endfor %}
70
 
71
- for (var k = tid; k < params.K; k = k + WG) {
 
72
  let block = k / params.blockSize;
73
- let offset = k % params.blockSize;
74
- let av = f32(a[k]);
 
 
 
 
 
 
 
 
 
75
  {% for g in range(groups) %}
76
  {% set sfx = "" if groups == 1 else g %}
77
  {% for c in range(4) %}
78
  {% set i = g * 4 + c %}
79
  {% if i == 0 %}
80
  let scale0 = f32(scales[(col_base + 0u) * params.kBlocks + block]);
81
- let q0 = f32(packed_weight(col_base + 0u, block, offset)) - zero_point({% if hasZero %}col_base + 0u, block{% endif %});
82
- acc{{ sfx }}.x = acc{{ sfx }}.x + av * q0 * scale0;
 
 
 
 
83
  {% else %}
84
  if (col_base + {{ i }}u < params.N) {
85
  let scale{{ i }} = f32(scales[(col_base + {{ i }}u) * params.kBlocks + block]);
86
- let q{{ i }} = f32(packed_weight(col_base + {{ i }}u, block, offset)) - zero_point({% if hasZero %}col_base + {{ i }}u, block{% endif %});
87
- acc{{ sfx }}.{{ comps[c] }} = acc{{ sfx }}.{{ comps[c] }} + av * q{{ i }} * scale{{ i }};
 
 
 
 
88
  }
89
  {% endif %}
90
  {% endfor %}
 
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 CODES: u32 = {{ codesPerByte }}u;
17
 
18
  {% for g in range(groups) %}
19
  {% set sfx = "" if groups == 1 else g %}
 
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 u32 per (col, block)
 
57
  var acc{{ sfx }} = vec4<f32>(0.0);
58
  {% endfor %}
59
 
60
+ // Stride whole bytes: every code a byte holds is consumed from one fetch.
61
+ for (var k = tid * CODES; k < params.K; k = k + WG * CODES) {
62
  let block = k / params.blockSize;
63
+ // blockSize is a power of two of at least 16, so a byte never straddles two
64
+ // blocks and every code below shares this block's scale and zero point.
65
+ let byte_index = (k % params.blockSize) / CODES;
66
+ {% for j in range(codesPerByte) %}
67
+ {% if j == 0 %}
68
+ let av0 = f32(a[k]);
69
+ {% else %}
70
+ // K need not be a multiple of CODES; a code past the end contributes zero.
71
+ let av{{ j }} = select(0.0, f32(a[min(k + {{ j }}u, params.K - 1u)]), k + {{ j }}u < params.K);
72
+ {% endif %}
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
  let scale0 = f32(scales[(col_base + 0u) * params.kBlocks + block]);
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 %}
build/webgpu/matmul-nbits-q4-sgmat.wgsl.jinja CHANGED
@@ -8,6 +8,9 @@
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;
 
 
 
11
  enable chromium_experimental_subgroup_matrix;
12
  {% if usesF16 %}
13
  enable f16;
@@ -118,7 +121,7 @@ fn storeOutput(offset: u32, col_base: u32, row: u32, col: u32, src_slot: u32, ro
118
  }
119
  }
120
 
121
- @compute @workgroup_size({{ workgroupSize }}, 1, 1)
122
  fn main(
123
  @builtin(workgroup_id) workgroup_id: vec3<u32>,
124
  @builtin(local_invocation_index) local_idx: u32,
@@ -145,14 +148,14 @@ fn main(
145
 
146
  for (var step = 0u; step < TILE_K; step = step + 8u) {
147
  {% set operandScalar = "f32" %} let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
148
- var matA0: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset, false, TILE_K);
149
- var matA1: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset + 8u * TILE_K, false, TILE_K);
150
 
151
  let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
152
- var matB0: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset, true, TILE_K);
153
- var matB1: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 8u * TILE_K, true, TILE_K);
154
- var matB2: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 16u * TILE_K, true, TILE_K);
155
- var matB3: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 24u * TILE_K, true, TILE_K);
156
 
157
  matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
158
  matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
@@ -172,7 +175,7 @@ fn main(
172
  // clobbering the first's still-in-flight readback when a partial final M-tile
173
  // diverges storeOutput's guard. Without both barriers the last valid row can be corrupted.
174
  {% for col in [0, 1, 2, 3] %}
175
- subgroupMatrixStore(&scratch[subtile_id][{{ col }}], 0u, matC0{{ col }}, false, 8u);
176
  {% endfor %}
177
  workgroupBarrier();
178
  let row = sg_id / 4u;
@@ -184,7 +187,7 @@ fn main(
184
  workgroupBarrier();
185
 
186
  {% for col in [0, 1, 2, 3] %}
187
- subgroupMatrixStore(&scratch[subtile_id][{{ col }}], 0u, matC1{{ col }}, false, 8u);
188
  {% endfor %}
189
  workgroupBarrier();
190
  matrix_c_offset = matrix_c_offset + 8u * N;
 
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;
11
+ {% if pinSubgroupSize32 %}
12
+ enable subgroup_size_control;
13
+ {% endif %}
14
  enable chromium_experimental_subgroup_matrix;
15
  {% if usesF16 %}
16
  enable f16;
 
121
  }
122
  }
123
 
124
+ @compute @workgroup_size({{ workgroupSize }}, 1, 1){{ " @subgroup_size(32)" if pinSubgroupSize32 else "" }}
125
  fn main(
126
  @builtin(workgroup_id) workgroup_id: vec3<u32>,
127
  @builtin(local_invocation_index) local_idx: u32,
 
148
 
149
  for (var step = 0u; step < TILE_K; step = step + 8u) {
150
  {% set operandScalar = "f32" %} let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
151
+ var matA0: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset, TILE_K);
152
+ var matA1: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset + 8u * TILE_K, TILE_K);
153
 
154
  let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
155
+ var matB0: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset, TILE_K);
156
+ var matB1: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 8u * TILE_K, TILE_K);
157
+ var matB2: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 16u * TILE_K, TILE_K);
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);
 
175
  // clobbering the first's still-in-flight readback when a partial final M-tile
176
  // diverges storeOutput's guard. Without both barriers the last valid row can be corrupted.
177
  {% for col in [0, 1, 2, 3] %}
178
+ subgroupMatrixStore<row_major>(&scratch[subtile_id][{{ col }}], 0u, matC0{{ col }}, 8u);
179
  {% endfor %}
180
  workgroupBarrier();
181
  let row = sg_id / 4u;
 
187
  workgroupBarrier();
188
 
189
  {% for col in [0, 1, 2, 3] %}
190
+ subgroupMatrixStore<row_major>(&scratch[subtile_id][{{ col }}], 0u, matC1{{ col }}, 8u);
191
  {% endfor %}
192
  workgroupBarrier();
193
  matrix_c_offset = matrix_c_offset + 8u * N;
build/webgpu/metadata.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "com.microsoft.MatMulNBits",
3
- "id": "_com_microsoft_matmulnbits_webgpu_81b817c",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
@@ -8,18 +8,18 @@
8
  "algorithm": "sha256",
9
  "files": {
10
  "bench.json": "BbpRtHBH/vkJDaybyRBNKhuUCpe4XMJtKmW3bhRcEZE=",
11
- "manifest.json": "Gz92F8En4SeyVGrSZBtQghet4kVvjHxGIplFWSIq7AM=",
12
  "matmul-nbits-dp4a-quantize.wgsl.jinja": "WEUGSDH5qWYg10f/mkAHxq8AkIkMW9R/Wn/Y23Q0Jw0=",
13
- "matmul-nbits-gemv-q4.wgsl.jinja": "r9eGhKkZPaNaUHFcDZh1JpS+w6z4Wow/7wGrLMVVgsA=",
14
  "matmul-nbits-q4-dp4a-prefill.wgsl.jinja": "qUrURZDpPzMpABnTgESkHgLCQiAWxEZ8wvzkAPTW2kI=",
15
  "matmul-nbits-q4-prefill-tile4x4.wgsl.jinja": "HjZraSjOpaSl5Cjns4scselOIn+ibxFEu+9LTeUI0QI=",
16
  "matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja": "oU7BcxJnKIEfgnkWHPq77RrCmrE0iIm6ED7cgVfZUYs=",
17
  "matmul-nbits-q4-prefill-tiled.wgsl.jinja": "TdetCiv5o88hvWCGNDewpFX+M+NZrz9rekU/d5kMZTM=",
18
- "matmul-nbits-q4-sgmat.wgsl.jinja": "8E4yGRvezICz5ctdW4g2w/HBfLvqJF2hA+jOobcuJio=",
19
  "matmul-nbits.wgsl.jinja": "DRkwODEER11pYQxt2ssS9YvFggO3CbUqQ9yag53J5lY=",
20
- "test.json": "2mpJ5fx2GnlZycPygUyHbOM37n7X6VZmUMGHNkJa7qw="
21
  }
22
  },
23
- "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
24
  "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.MatMulNBits" }
25
  }
 
1
  {
2
  "name": "com.microsoft.MatMulNBits",
3
+ "id": "_com_microsoft_matmulnbits_webgpu_5495bd2",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
 
8
  "algorithm": "sha256",
9
  "files": {
10
  "bench.json": "BbpRtHBH/vkJDaybyRBNKhuUCpe4XMJtKmW3bhRcEZE=",
11
+ "manifest.json": "z8oCYWRaMHew5ogYuf7Txv4/6y2qpVoVODDlLxEZ+vA=",
12
  "matmul-nbits-dp4a-quantize.wgsl.jinja": "WEUGSDH5qWYg10f/mkAHxq8AkIkMW9R/Wn/Y23Q0Jw0=",
13
+ "matmul-nbits-gemv-q4.wgsl.jinja": "x1LxVx2cJBdN2cqapXz2T6AScpTcbmTO+B3tzvIXuT8=",
14
  "matmul-nbits-q4-dp4a-prefill.wgsl.jinja": "qUrURZDpPzMpABnTgESkHgLCQiAWxEZ8wvzkAPTW2kI=",
15
  "matmul-nbits-q4-prefill-tile4x4.wgsl.jinja": "HjZraSjOpaSl5Cjns4scselOIn+ibxFEu+9LTeUI0QI=",
16
  "matmul-nbits-q4-prefill-tiled-reg.wgsl.jinja": "oU7BcxJnKIEfgnkWHPq77RrCmrE0iIm6ED7cgVfZUYs=",
17
  "matmul-nbits-q4-prefill-tiled.wgsl.jinja": "TdetCiv5o88hvWCGNDewpFX+M+NZrz9rekU/d5kMZTM=",
18
+ "matmul-nbits-q4-sgmat.wgsl.jinja": "PfO9QrUyNflp7RtUbUTRDwx7GDI6UUwz9I+6mJpO1OA=",
19
  "matmul-nbits.wgsl.jinja": "DRkwODEER11pYQxt2ssS9YvFggO3CbUqQ9yag53J5lY=",
20
+ "test.json": "XLwnbLdOJASke2yBnDScjJi+o2XISmdRCebb2jwWpDI="
21
  }
22
  },
23
+ "provenance": { "kernel": { "sha": "c928d21e6cc1310861cba3bafb75f5f679ecf5f3", "dirty": false } },
24
  "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.MatMulNBits" }
25
  }
build/webgpu/test.json CHANGED
@@ -858,6 +858,56 @@
858
  "outputs": { "yT": { "dtype": "float32", "shape": [1, 7], "tolerance": 0.00001 } },
859
  "attrs": { "K": 32, "N": 7, "bits": 4, "block_size": 32 }
860
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
861
  {
862
  "name": "q4_gemv_default_zero_m1_n13_ncols8",
863
  "tunables": { "GEMV_N_COLS": 8 },
 
858
  "outputs": { "yT": { "dtype": "float32", "shape": [1, 7], "tolerance": 0.00001 } },
859
  "attrs": { "K": 32, "N": 7, "bits": 4, "block_size": 32 }
860
  },
861
+ {
862
+ "name": "q4_gemv_default_zero_m1_odd_k33_n5",
863
+ "provenance": {
864
+ "notes": "K=33 is odd, so the last stored byte of the second block carries one live nibble and one past the end of K. The GEMV walks whole bytes, so this is the only shape that exercises its out-of-range activation guard; every other M==1 case has an even K."
865
+ },
866
+ "inputs": {
867
+ "aT": {
868
+ "dtype": "float32",
869
+ "shape": [1, 33],
870
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
871
+ },
872
+ "bT": {
873
+ "dtype": "uint8",
874
+ "shape": [5, 2, 16],
875
+ "data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/q4_weight_cycle_b_t" } }
876
+ },
877
+ "scalesT": {
878
+ "dtype": "float32",
879
+ "shape": [5, 2],
880
+ "data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/quant_scale_cycle_t" } }
881
+ }
882
+ },
883
+ "outputs": { "yT": { "dtype": "float32", "shape": [1, 5], "tolerance": 0.00001 } },
884
+ "attrs": { "K": 33, "N": 5, "bits": 4, "block_size": 32 }
885
+ },
886
+ {
887
+ "name": "q2_gemv_default_zero_m1_k18_n5",
888
+ "provenance": {
889
+ "notes": "The only two-bit M==1 case: four codes share one stored byte, and K=18 leaves two of the last byte's four codes past the end of K. Covers both the two-bit unpack in the GEMV and its out-of-range activation guard at the widest code-per-byte packing."
890
+ },
891
+ "inputs": {
892
+ "aT": {
893
+ "dtype": "float32",
894
+ "shape": [1, 18],
895
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
896
+ },
897
+ "bT": {
898
+ "dtype": "uint8",
899
+ "shape": [5, 2, 4],
900
+ "data": { "kind": "cycle", "values": [27, 180, 75, 226, 33, 150, 201, 108, 57, 246, 129, 66, 195, 84, 237] }
901
+ },
902
+ "scalesT": {
903
+ "dtype": "float32",
904
+ "shape": [5, 2],
905
+ "data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/quant_scale_cycle_t" } }
906
+ }
907
+ },
908
+ "outputs": { "yT": { "dtype": "float32", "shape": [1, 5], "tolerance": 0.00001 } },
909
+ "attrs": { "K": 18, "N": 5, "bits": 2, "block_size": 16 }
910
+ },
911
  {
912
  "name": "q4_gemv_default_zero_m1_n13_ncols8",
913
  "tunables": { "GEMV_N_COLS": 8 },