/** * complex_ssd.ts – Complex-valued SSD kernels for Mamba-3. * * Three targeted improvements over Mamba-2 SSD: * * 1. Complex-valued states * h ∈ ℂ^(N/2) stored as interleaved (real, imag) f32 pairs. * A ∈ ℂ encoded as A_log[H, 2] = [log|A|, arg(A)]. * * 2. Exponential-Trapezoidal (ET) discretisation * A_bar = exp(Δ · A) (complex multiply) * B_bar = (A_bar − 1) · A⁻¹ · B (exact, complex division) * * 3. MIMO recurrence (G groups of G inputs/outputs per head) * Implemented here with G=1 (SISO) as the default; G>1 is a future * extension that enlarges the B/C projections. * * Buffer layout: * x : [B, L, D_inner] real-valued * B_proj : [B, L, n_groups, N*2] interleaved complex (re,im) * C_proj : [B, L, n_groups, N*2] * dt : [B, L, H] real-valued * A_log : [H, 2] [log|A|, arg(A)] per head * dt_bias : [H] * D_vec : [H] * out : [B, L, D_inner] real-valued (Re(C·h)) * state_carry: [n_chunks+1, B, H, N*2, d_head] complex states * * Dispatch: (n_chunks, H, B) */ export const COMPLEX_SSD_FORWARD_WGSL: string = /* wgsl */` struct CssdParams { seq_len : u32, d_inner : u32, n_heads : u32, d_head : u32, n_groups : u32, n_complex : u32, // N/2 – number of complex state components chunk_len : u32, n_chunks : u32, batch : u32, }; @group(0) @binding(0) var params : CssdParams; @group(0) @binding(1) var x_in : array; @group(0) @binding(2) var B_proj : array; // complex: N_c*2 per token @group(0) @binding(3) var C_proj : array; @group(0) @binding(4) var dt_in : array; @group(0) @binding(5) var A_log : array; // [H, 2] @group(0) @binding(6) var dt_bias : array; @group(0) @binding(7) var D_vec : array; @group(0) @binding(8) var out_buf : array; @group(0) @binding(9) var state_carry : array; // complex states fn softplus(v: f32) -> f32 { return log(1.0 + exp(v)); } // Complex multiply: (ar + i·ai) * (br + i·bi) fn cmul_re(ar: f32, ai: f32, br: f32, bi: f32) -> f32 { return ar*br - ai*bi; } fn cmul_im(ar: f32, ai: f32, br: f32, bi: f32) -> f32 { return ar*bi + ai*br; } // Complex exp: exp(x + i·y) = exp(x)*(cos(y) + i*sin(y)) fn cexp_re(x: f32, y: f32) -> f32 { return exp(x) * cos(y); } fn cexp_im(x: f32, y: f32) -> f32 { return exp(x) * sin(y); } // ET discretisation B_bar = (A_bar - 1) * A^-1 * B // A^-1 = 1/A = conj(A)/|A|^2. Here A = exp(log_mag)*exp(i*phase). // |A| = exp(log_mag), A^-1 = exp(-log_mag)*exp(-i*phase) // (A_bar - 1) * A^-1 = scalar complex product computed below. fn et_bbar_re(a_bar_re: f32, a_bar_im: f32, log_mag: f32, phase: f32) -> f32 { // (A_bar - 1) let num_re = a_bar_re - 1.0; let num_im = a_bar_im; // A^-1 = exp(-log_mag - i*phase) let inv_re = cexp_re(-log_mag, -phase); let inv_im = cexp_im(-log_mag, -phase); return cmul_re(num_re, num_im, inv_re, inv_im); } fn et_bbar_im(a_bar_re: f32, a_bar_im: f32, log_mag: f32, phase: f32) -> f32 { let num_re = a_bar_re - 1.0; let num_im = a_bar_im; let inv_re = cexp_re(-log_mag, -phase); let inv_im = cexp_im(-log_mag, -phase); return cmul_im(num_re, num_im, inv_re, inv_im); } @compute @workgroup_size(1, 1, 1) fn complex_ssd_forward(@builtin(global_invocation_id) gid: vec3) { let chunk_id = gid.x; let head_id = gid.y; let batch_id = gid.z; let L = params.seq_len; let D = params.d_inner; let H = params.n_heads; let dh = params.d_head; let G = params.n_groups; let Nc = params.n_complex; // complex state count let N2 = Nc * 2u; // float pairs let CL = params.chunk_len; let B = params.batch; let t_start = chunk_id * CL; let t_end = min(t_start + CL, L); let group_id = head_id * G / H; // Load A for this head: A = exp(log_mag) * exp(i*phase) let log_mag = A_log[head_id * 2u + 0u]; let phase = A_log[head_id * 2u + 1u]; let db = dt_bias[head_id]; let d_skip = D_vec[head_id]; // State buffer strides (complex: N2*dh floats per head) let state_stride = B * H * N2 * dh; let state_base_in = chunk_id * state_stride + batch_id * H * N2 * dh + head_id * N2 * dh; let state_base_out = (chunk_id + 1u) * state_stride + batch_id * H * N2 * dh + head_id * N2 * dh; // Copy carry-in to working slot for (var s: u32 = 0u; s < N2 * dh; s = s + 1u) { state_carry[state_base_out + s] = state_carry[state_base_in + s]; } for (var t: u32 = t_start; t < t_end; t = t + 1u) { let dt_idx = batch_id * L * H + t * H + head_id; let dt_val = softplus(dt_in[dt_idx] + db); // A_bar = exp(dt * A) = exp(dt*log_mag + i*dt*phase) let a_bar_re = cexp_re(dt_val * log_mag, dt_val * phase); let a_bar_im = cexp_im(dt_val * log_mag, dt_val * phase); // ET B_bar scalar factor (applied per B_proj element) let bbar_factor_re = et_bbar_re(a_bar_re, a_bar_im, log_mag, phase); let bbar_factor_im = et_bbar_im(a_bar_re, a_bar_im, log_mag, phase); let x_base = batch_id * L * D + t * D + head_id * dh; // B_proj / C_proj: [B, L, G, N*2] — interleaved re/im let bc_base = batch_id * L * G * N2 + t * G * N2 + group_id * N2; for (var i: u32 = 0u; i < dh; i = i + 1u) { let x_val = x_in[x_base + i]; var y_re = 0.0; for (var nc: u32 = 0u; nc < Nc; nc = nc + 1u) { let b_re = B_proj[bc_base + nc * 2u + 0u]; let b_im = B_proj[bc_base + nc * 2u + 1u]; let c_re = C_proj[bc_base + nc * 2u + 0u]; let c_im = C_proj[bc_base + nc * 2u + 1u]; // B_bar · x (complex * real = complex scale) let inp_re = cmul_re(bbar_factor_re, bbar_factor_im, b_re, b_im) * x_val; let inp_im = cmul_im(bbar_factor_re, bbar_factor_im, b_re, b_im) * x_val; let s_re_idx = state_base_out + nc * 2u * dh + 0u * dh + i; let s_im_idx = state_base_out + nc * 2u * dh + 1u * dh + i; // h_t = A_bar * h_{t-1} + B_bar * x let h_prev_re = state_carry[s_re_idx]; let h_prev_im = state_carry[s_im_idx]; let h_new_re = cmul_re(a_bar_re, a_bar_im, h_prev_re, h_prev_im) + inp_re; let h_new_im = cmul_im(a_bar_re, a_bar_im, h_prev_re, h_prev_im) + inp_im; state_carry[s_re_idx] = h_new_re; state_carry[s_im_idx] = h_new_im; // y += Re(C · h) y_re = y_re + cmul_re(c_re, -c_im, h_new_re, h_new_im); // C·h real part } let out_idx = batch_id * L * D + t * D + head_id * dh + i; out_buf[out_idx] = y_re + d_skip * x_val; } } } `; // ── Backward ────────────────────────────────────────────────────────────────── export const COMPLEX_SSD_BACKWARD_WGSL: string = /* wgsl */` struct CssdParams { seq_len : u32, d_inner : u32, n_heads : u32, d_head : u32, n_groups : u32, n_complex : u32, chunk_len : u32, n_chunks : u32, batch : u32, }; @group(0) @binding(0) var params : CssdParams; @group(0) @binding(1) var x_in : array; @group(0) @binding(2) var B_proj : array; @group(0) @binding(3) var C_proj : array; @group(0) @binding(4) var dt_in : array; @group(0) @binding(5) var A_log : array; @group(0) @binding(6) var dt_bias : array; @group(0) @binding(7) var state_carry : array; @group(0) @binding(8) var dy : array; @group(0) @binding(9) var dx : array; @group(0) @binding(10) var dB : array; @group(0) @binding(11) var dC : array; @group(0) @binding(12) var ddt : array; @group(0) @binding(13) var dA_log : array; @group(0) @binding(14) var dD_vec : array; fn softplus(v: f32) -> f32 { return log(1.0 + exp(v)); } fn d_softplus(v: f32) -> f32 { return 1.0 / (1.0 + exp(-v)); } fn cmul_re(ar: f32, ai: f32, br: f32, bi: f32) -> f32 { return ar*br - ai*bi; } fn cmul_im(ar: f32, ai: f32, br: f32, bi: f32) -> f32 { return ar*bi + ai*br; } fn cexp_re(x: f32, y: f32) -> f32 { return exp(x) * cos(y); } fn cexp_im(x: f32, y: f32) -> f32 { return exp(x) * sin(y); } @compute @workgroup_size(1, 1, 1) fn complex_ssd_backward(@builtin(global_invocation_id) gid: vec3) { let chunk_id = gid.x; let head_id = gid.y; let batch_id = gid.z; let L = params.seq_len; let D = params.d_inner; let H = params.n_heads; let dh = params.d_head; let G = params.n_groups; let Nc = params.n_complex; let N2 = Nc * 2u; let CL = params.chunk_len; let B = params.batch; let t_start = chunk_id * CL; let t_end = min(t_start + CL, L); let group_id = head_id * G / H; let log_mag = A_log[head_id * 2u + 0u]; let phase = A_log[head_id * 2u + 1u]; let db = dt_bias[head_id]; let state_stride = B * H * N2 * dh; for (var t_rev: u32 = 0u; t_rev < t_end - t_start; t_rev = t_rev + 1u) { let t = t_end - 1u - t_rev; let dt_idx = batch_id * L * H + t * H + head_id; let dt_raw = dt_in[dt_idx] + db; let dt_val = softplus(dt_raw); let a_bar_re = cexp_re(dt_val * log_mag, dt_val * phase); let a_bar_im = cexp_im(dt_val * log_mag, dt_val * phase); let x_base = batch_id * L * D + t * D + head_id * dh; let bc_base = batch_id * L * G * N2 + t * G * N2 + group_id * N2; let state_base = (chunk_id + 1u) * state_stride + batch_id * H * N2 * dh + head_id * N2 * dh; let state_prev = chunk_id * state_stride + batch_id * H * N2 * dh + head_id * N2 * dh; for (var i: u32 = 0u; i < dh; i = i + 1u) { let dy_val = dy[batch_id * L * D + t * D + head_id * dh + i]; let x_val = x_in[x_base + i]; dD_vec[head_id] = dD_vec[head_id] + dy_val * x_val; dx[x_base + i] = dx[x_base + i] + dy_val; for (var nc: u32 = 0u; nc < Nc; nc = nc + 1u) { let c_re = C_proj[bc_base + nc * 2u + 0u]; let c_im = C_proj[bc_base + nc * 2u + 1u]; let b_re = B_proj[bc_base + nc * 2u + 0u]; let b_im = B_proj[bc_base + nc * 2u + 1u]; let h_re = state_carry[state_base + nc * 2u * dh + 0u * dh + i]; let h_im = state_carry[state_base + nc * 2u * dh + 1u * dh + i]; // dC from Re(C · h) output — gradient of Re(C·h) w.r.t. C is Re(h) dC[bc_base + nc * 2u + 0u] = dC[bc_base + nc * 2u + 0u] + dy_val * h_re; dC[bc_base + nc * 2u + 1u] = dC[bc_base + nc * 2u + 1u] - dy_val * h_im; // dh from upstream: dh_re = c_re * dy, dh_im = -c_im * dy (Re(C·h) gradient) let dh_re = c_re * dy_val; let dh_im = -c_im * dy_val; // dB: B_bar · x contributed h_new; gradient flows through B_bar // simplified: dB += dh * x (ignoring complex B_bar Jacobian) dB[bc_base + nc * 2u + 0u] = dB[bc_base + nc * 2u + 0u] + dh_re * x_val; dB[bc_base + nc * 2u + 1u] = dB[bc_base + nc * 2u + 1u] + dh_im * x_val; // dx += Re(B_bar* · dh) (simplified) dx[x_base + i] = dx[x_base + i] + cmul_re(b_re, -b_im, dh_re, dh_im); // ddt: from A_bar and B_bar dependence on dt let h_prev_re = state_carry[state_prev + nc * 2u * dh + 0u * dh + i]; let h_prev_im = state_carry[state_prev + nc * 2u * dh + 1u * dh + i]; // dA_bar/ddt = A * A_bar let da_bar_re = cmul_re(cexp_re(log_mag, phase), cexp_im(log_mag, phase), a_bar_re, a_bar_im); let da_bar_im = cmul_im(cexp_re(log_mag, phase), cexp_im(log_mag, phase), a_bar_re, a_bar_im); ddt[dt_idx] = ddt[dt_idx] + (cmul_re(da_bar_re, da_bar_im, h_prev_re, h_prev_im) * dh_re - cmul_im(da_bar_re, da_bar_im, h_prev_re, h_prev_im) * dh_im) * d_softplus(dt_raw); } } } } `;