|
| 1 | +/* |
| 2 | + * Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | + * All rights reserved. |
| 4 | + * |
| 5 | + * This source code is licensed under the BSD-style license found in the |
| 6 | + * LICENSE file in the root directory of this source tree. |
| 7 | + */ |
| 8 | + |
| 9 | +#pragma once |
| 10 | + |
| 11 | +#include <cstdint> |
| 12 | + |
| 13 | +namespace executorch::backends::webgpu { |
| 14 | + |
| 15 | +// @generated from q4gsw_linear_gemm_qkv_fused.wgsl - DO NOT EDIT. |
| 16 | +// wgsl-sha256: 93e127e8ee4609d846015c8b75a600a29502e19a92bdf3a08e3429635f834085 |
| 17 | +inline constexpr const char* kQ4gswLinearGemmQkvFusedWGSL = R"( |
| 18 | +enable f16; |
| 19 | +// Fused QKV q4gsw GEMM (Llama attention projections): one [M, N=3072] pwdq + f16-accumulate GEMM |
| 20 | +// (vec4<f32> activation load) that scatter-writes each output column range to a SEPARATE buffer -- |
| 21 | +// c<2048 -> q, [2048,2560) -> k, [2560,3072) -> v. Replaces the 3 separate q/k/v linear dispatches; |
| 22 | +// fixes the N=512 K/V occupancy starvation (16 WGs -> 96 WGs at M~128). Boundaries are 64-tile-aligned |
| 23 | +// so each 64-col tile maps to exactly one output (uniform branch per workgroup). Per-output ROW STRIDE: |
| 24 | +// q=2048, k=v=512. BIT-EXACT to 3 separate pwdqf16acc linears (fusing along N does not change the |
| 25 | +// per-column K-accumulation order). Validated on Canary M4 Pro: correct (maxRel ~1e-3), scatter overhead |
| 26 | +// 1.02x (free), concat win 1.63x on the QKV block. Boundaries hardcoded for Llama-3.2-1B GQA (32Q/8KV). |
| 27 | +@group(0) @binding(0) var<storage, read_write> t_out_q: array<f32>; |
| 28 | +@group(0) @binding(1) var<storage, read_write> t_out_k: array<f32>; |
| 29 | +@group(0) @binding(2) var<storage, read_write> t_out_v: array<f32>; |
| 30 | +@group(0) @binding(3) var<storage, read> t_input: array<vec4<f32>>; |
| 31 | +@group(0) @binding(4) var<storage, read> t_weight: array<u32>; |
| 32 | +@group(0) @binding(5) var<storage, read> t_scales: array<f32>; |
| 33 | +@group(0) @binding(6) var<storage, read> t_bias: array<f32>; |
| 34 | +struct Params { |
| 35 | + M: u32, |
| 36 | + N: u32, |
| 37 | + K: u32, |
| 38 | + K_packed: u32, |
| 39 | + group_size: u32, |
| 40 | + padded_N: u32, |
| 41 | + has_bias: u32, |
| 42 | + _pad: u32, |
| 43 | +} |
| 44 | +@group(0) @binding(7) var<uniform> params: Params; |
| 45 | +const BM: u32 = 64u; const BN: u32 = 64u; const BK: u32 = 16u; |
| 46 | +const N_Q: u32 = 2048u; const N_QK: u32 = 2560u; const N_KV: u32 = 512u; |
| 47 | +var<workgroup> As: array<f16, 1024>; |
| 48 | +var<workgroup> Bs: array<f16, 1024>; |
| 49 | +@compute @workgroup_size(16, 16) |
| 50 | +fn main(@builtin(workgroup_id) wid: vec3<u32>, |
| 51 | + @builtin(local_invocation_id) lid: vec3<u32>) { |
| 52 | + let nbN = (params.N + BN - 1u) / BN; |
| 53 | + let bx = wid.x % nbN; |
| 54 | + let by = wid.x / nbN; |
| 55 | + let row0 = by * BM; |
| 56 | + let col0 = bx * BN; |
| 57 | + let tid = lid.y * 16u + lid.x; |
| 58 | + var acc: array<array<f16, 4>, 4>; |
| 59 | + for (var m: u32 = 0u; m < 4u; m = m + 1u) { |
| 60 | + for (var n: u32 = 0u; n < 4u; n = n + 1u) { acc[m][n] = 0.0h; } |
| 61 | + } |
| 62 | + let ar = tid / 4u; |
| 63 | + let ac = (tid % 4u) * 4u; |
| 64 | + var k0: u32 = 0u; |
| 65 | + loop { |
| 66 | + if (k0 >= params.K) { break; } |
| 67 | + let arow = row0 + ar; |
| 68 | + if (arow < params.M) { |
| 69 | + let base = arow * params.K + k0 + ac; |
| 70 | + let av = t_input[base >> 2u]; |
| 71 | + As[ar * BK + ac + 0u] = f16(av.x); As[ar * BK + ac + 1u] = f16(av.y); |
| 72 | + As[ar * BK + ac + 2u] = f16(av.z); As[ar * BK + ac + 3u] = f16(av.w); |
| 73 | + } else { |
| 74 | + As[ar * BK + ac + 0u] = 0.0h; As[ar * BK + ac + 1u] = 0.0h; |
| 75 | + As[ar * BK + ac + 2u] = 0.0h; As[ar * BK + ac + 3u] = 0.0h; |
| 76 | + } |
| 77 | + if (tid < BN) { |
| 78 | + let c = tid; |
| 79 | + let n = col0 + c; |
| 80 | + if (n < params.N) { |
| 81 | + let scale_row = (k0 / params.group_size) * params.padded_N; |
| 82 | + let scale = f16(t_scales[scale_row + n]); |
| 83 | + let base_word = n * (params.K_packed >> 2u) + (k0 >> 3u); |
| 84 | + let w0 = t_weight[base_word]; |
| 85 | + let w1 = t_weight[base_word + 1u]; |
| 86 | + for (var br: u32 = 0u; br < BK; br = br + 1u) { |
| 87 | + let word = select(w1, w0, br < 8u); |
| 88 | + let nib = (word >> ((br & 7u) * 4u)) & 0x0Fu; |
| 89 | + Bs[br * BN + c] = f16(i32(nib) - 8) * scale; |
| 90 | + } |
| 91 | + } else { |
| 92 | + for (var br: u32 = 0u; br < BK; br = br + 1u) { Bs[br * BN + c] = 0.0h; } |
| 93 | + } |
| 94 | + } |
| 95 | + workgroupBarrier(); |
| 96 | + for (var k: u32 = 0u; k < BK; k = k + 1u) { |
| 97 | + var a: array<f16, 4>; |
| 98 | + var bvec: array<f16, 4>; |
| 99 | + for (var m: u32 = 0u; m < 4u; m = m + 1u) { a[m] = As[(lid.y * 4u + m) * BK + k]; } |
| 100 | + for (var n: u32 = 0u; n < 4u; n = n + 1u) { bvec[n] = Bs[k * BN + lid.x * 4u + n]; } |
| 101 | + for (var m: u32 = 0u; m < 4u; m = m + 1u) { |
| 102 | + for (var n: u32 = 0u; n < 4u; n = n + 1u) { acc[m][n] = fma(a[m], bvec[n], acc[m][n]); } |
| 103 | + } |
| 104 | + } |
| 105 | + workgroupBarrier(); |
| 106 | + k0 = k0 + BK; |
| 107 | + } |
| 108 | + for (var m: u32 = 0u; m < 4u; m = m + 1u) { |
| 109 | + for (var n: u32 = 0u; n < 4u; n = n + 1u) { |
| 110 | + let r = row0 + lid.y * 4u + m; |
| 111 | + let c = col0 + lid.x * 4u + n; // global fused column [0, 3072) |
| 112 | + if (r < params.M && c < params.N) { |
| 113 | + var val = f32(acc[m][n]); |
| 114 | + if (params.has_bias != 0u) { val = val + t_bias[c]; } |
| 115 | + if (c < N_Q) { t_out_q[r * N_Q + c] = val; } |
| 116 | + else if (c < N_QK) { t_out_k[r * N_KV + (c - N_Q)] = val; } |
| 117 | + else { t_out_v[r * N_KV + (c - N_QK)] = val; } |
| 118 | + } |
| 119 | + } |
| 120 | + } |
| 121 | +} |
| 122 | +)"; |
| 123 | + |
| 124 | +inline constexpr uint32_t kQ4gswLinearGemmQkvFusedWorkgroupSizeX = 16; |
| 125 | +inline constexpr uint32_t kQ4gswLinearGemmQkvFusedWorkgroupSizeY = 16; |
| 126 | +inline constexpr uint32_t kQ4gswLinearGemmQkvFusedWorkgroupSizeZ = 1; |
| 127 | + |
| 128 | +} // namespace executorch::backends::webgpu |
0 commit comments