// Linear Projection WGSL Kernel // General-purpose matrix multiplication: Y = X @ W^T + b // Supports the up-projection and down-projection linear layers in the Mamba block. // // Shapes: // X : (batch * seq_len, in_features) – input (rows) // W : (out_features, in_features) – weight matrix (row-major) // b : (out_features,) – bias // Y : (batch * seq_len, out_features) – output export const LINEAR_FORWARD_WGSL: string = /* wgsl */` struct LinearParams { M : u32, // number of rows (batch * seq_len) K : u32, // in_features N : u32, // out_features }; @group(0) @binding(0) var params : LinearParams; @group(0) @binding(1) var X : array; // (M, K) @group(0) @binding(2) var W : array; // (N, K) @group(0) @binding(3) var bias : array; // (N,) @group(0) @binding(4) var Y : array; // (M, N) // Tiled matmul using workgroup shared memory (16x16 tiles) var tile_X : array; // 16 * 16 var tile_W : array; @compute @workgroup_size(16, 16, 1) fn linear_forward( @builtin(global_invocation_id) gid : vec3, @builtin(local_invocation_id) lid : vec3, @builtin(workgroup_id) wid : vec3, ) { let M = params.M; let K = params.K; let N = params.N; let row = gid.x; // output row (M dimension) let col = gid.y; // output col (N dimension) var acc: f32 = 0.0; let TILE: u32 = 16u; let num_tiles = (K + TILE - 1u) / TILE; for (var tile_idx: u32 = 0u; tile_idx < num_tiles; tile_idx = tile_idx + 1u) { // Load X tile: shape (TILE_M, TILE_K) let x_col = tile_idx * TILE + lid.y; let x_row = wid.x * TILE + lid.x; if (x_row < M && x_col < K) { tile_X[lid.x * TILE + lid.y] = X[x_row * K + x_col]; } else { tile_X[lid.x * TILE + lid.y] = 0.0; } // Load W tile: shape (TILE_N, TILE_K) — W is (N, K) let w_col = tile_idx * TILE + lid.x; // K dimension let w_row = wid.y * TILE + lid.y; // N dimension if (w_row < N && w_col < K) { tile_W[lid.y * TILE + lid.x] = W[w_row * K + w_col]; } else { tile_W[lid.y * TILE + lid.x] = 0.0; } workgroupBarrier(); // Dot product within tile for (var k: u32 = 0u; k < TILE; k = k + 1u) { acc = acc + tile_X[lid.x * TILE + k] * tile_W[lid.y * TILE + k]; } workgroupBarrier(); } if (row < M && col < N) { Y[row * N + col] = acc + bias[col]; } } `; // ---- Backward pass for linear projection ---- export const LINEAR_BACKWARD_WGSL: string = /* wgsl */` struct LinearParams { M : u32, K : u32, N : u32, }; @group(0) @binding(0) var params : LinearParams; @group(0) @binding(1) var X : array; // (M, K) @group(0) @binding(2) var W : array; // (N, K) @group(0) @binding(3) var dY : array; // (M, N) @group(0) @binding(4) var dX : array; // (M, K) @group(0) @binding(5) var dW : array; // (N, K) @group(0) @binding(6) var db : array; // (N,) // Dispatch: (ceil(M/16), ceil(K/16), 1) – computes dX = dY @ W var tile_dY : array; var tile_W : array; @compute @workgroup_size(16, 16, 1) fn linear_backward_dX( @builtin(global_invocation_id) gid : vec3, @builtin(local_invocation_id) lid : vec3, @builtin(workgroup_id) wid : vec3, ) { let M = params.M; let K = params.K; let N = params.N; let row = gid.x; // M let col = gid.y; // K var acc: f32 = 0.0; let TILE: u32 = 16u; let num_tiles = (N + TILE - 1u) / TILE; for (var tile_idx: u32 = 0u; tile_idx < num_tiles; tile_idx = tile_idx + 1u) { // tile_dY: (M, TILE_N) slice let dy_col = tile_idx * TILE + lid.y; let dy_row = wid.x * TILE + lid.x; if (dy_row < M && dy_col < N) { tile_dY[lid.x * TILE + lid.y] = dY[dy_row * N + dy_col]; } else { tile_dY[lid.x * TILE + lid.y] = 0.0; } // tile_W: (TILE_N, K) slice — W[n, k] let w_row = tile_idx * TILE + lid.x; // N let w_col = wid.y * TILE + lid.y; // K if (w_row < N && w_col < K) { tile_W[lid.x * TILE + lid.y] = W[w_row * K + w_col]; } else { tile_W[lid.x * TILE + lid.y] = 0.0; } workgroupBarrier(); for (var n: u32 = 0u; n < TILE; n = n + 1u) { acc = acc + tile_dY[lid.x * TILE + n] * tile_W[n * TILE + lid.y]; } workgroupBarrier(); } if (row < M && col < K) { dX[row * K + col] = acc; } } // Dispatch: (ceil(N/16), ceil(K/16), 1) – computes dW = dY^T @ X var tile_dY2 : array; var tile_X2 : array; @compute @workgroup_size(16, 16, 1) fn linear_backward_dW( @builtin(global_invocation_id) gid : vec3, @builtin(local_invocation_id) lid : vec3, @builtin(workgroup_id) wid : vec3, ) { let M = params.M; let K = params.K; let N = params.N; let row = gid.x; // N let col = gid.y; // K var acc: f32 = 0.0; let TILE: u32 = 16u; let num_tiles = (M + TILE - 1u) / TILE; for (var tile_idx: u32 = 0u; tile_idx < num_tiles; tile_idx = tile_idx + 1u) { // dY^T tile: [N, M] accessed as dY[m, n] let m_idx = tile_idx * TILE + lid.y; let n_idx = wid.x * TILE + lid.x; if (n_idx < N && m_idx < M) { tile_dY2[lid.x * TILE + lid.y] = dY[m_idx * N + n_idx]; } else { tile_dY2[lid.x * TILE + lid.y] = 0.0; } // X tile: [M, K] let xm = tile_idx * TILE + lid.x; let xk = wid.y * TILE + lid.y; if (xm < M && xk < K) { tile_X2[lid.x * TILE + lid.y] = X[xm * K + xk]; } else { tile_X2[lid.x * TILE + lid.y] = 0.0; } workgroupBarrier(); for (var m: u32 = 0u; m < TILE; m = m + 1u) { acc = acc + tile_dY2[lid.x * TILE + m] * tile_X2[m * TILE + lid.y]; } workgroupBarrier(); } if (row < N && col < K) { dW[row * K + col] = acc; } } // Dispatch: (N, 1, 1) – accumulates db = sum_M dY @compute @workgroup_size(64, 1, 1) fn linear_backward_db( @builtin(global_invocation_id) gid : vec3, ) { let M = params.M; let N = params.N; let n = gid.x; if (n >= N) { return; } var acc: f32 = 0.0; for (var m: u32 = 0u; m < M; m = m + 1u) { acc = acc + dY[m * N + n]; } db[n] = acc; } `;