{% if usesF16 %} enable f16; {% endif %} {{ env.wgsl.resourceDeclarations }} const HIDDEN: u32 = {{ hiddenSize }}u; const SPATIAL: u32 = {{ spatial }}u; const CPG: u32 = {{ channelsPerGroup }}u; const GROUPS: u32 = {{ numGroups }}u; const WG: u32 = {{ workgroupSize }}u; const SPLIT: u32 = {{ split }}u; const EPSILON: f32 = {{ epsilon }}; @compute @workgroup_size(WG, 1, 1) fn main(@builtin(workgroup_id) wg: vec3, @builtin(local_invocation_id) lid: vec3) { let row = wg.x; let part = wg.z; if (row >= params.rows) { return; } var pair = vec2(0.0); for (var p = 0u; p < SPLIT; p = p + 1u) { pair = pair + partials[row * SPLIT + p]; } let base = row * HIDDEN; let shift = f32(x[base]); let mean_d = pair.x / f32(HIDDEN); let variance = max(pair.y / f32(HIDDEN) - mean_d * mean_d, 0.0); let mean = shift + mean_d; let inv_std = inverseSqrt(variance + EPSILON); let group = row % GROUPS; let chunk = (HIDDEN + SPLIT - 1u) / SPLIT; let start = part * chunk; let end = min(start + chunk, HIDDEN); for (var d = start + lid.x; d < end; d = d + WG) { let channel = group * CPG + d / SPATIAL; let index = base + d; y[index] = {{ scalar }}((f32(x[index]) - mean) * inv_std * f32(scale[channel]) + f32(bias[channel])); } }