{% macro sparse_schedule() %} // How much history precedes this call's tokens. Prompt mode is determined from // scalar total_sequence_length rather than per-batch values and forces the past // length to zero; a padded prompt row must not be read as if it had history. fn past_sequence_length(batch: u32) -> u32 { if (u32(total_sequence_length[0]) == params.seqLen) { return 0u; } let total = u32(key_total_sequence_lengths[batch]); return select(0u, total - params.seqLen, total >= params.seqLen); } {%- endmacro %} {% macro sparse_rotary(interleaved) %} // Which cos/sin entry a component uses, and which member of its rotation pair it is. // The two layouts differ only here: the NeoX split pairs d with d + ROTARY_HALF, and the // interleaved layout pairs the even lane with the odd one beside it. fn rotary_slot(d: u32) -> u32 { {% if interleaved %} return d / 2u; {% else %} return select(d - ROTARY_HALF, d, d < ROTARY_HALF); {% endif %} } fn rotary_partner(d: u32) -> u32 { {% if interleaved %} return select(d - 1u, d + 1u, (d % 2u) == 0u); {% else %} return select(d - ROTARY_HALF, d + ROTARY_HALF, d < ROTARY_HALF); {% endif %} } fn rotary_is_first(d: u32) -> bool { {% if interleaved %} return (d % 2u) == 0u; {% else %} return d < ROTARY_HALF; {% endif %} } // One component of the rotation, written from the component's own point of view so a // single expression covers both members of the pair: the leading one subtracts its // partner's sine term, the trailing one adds it. fn rotary_value(own: f32, partner: f32, cs: f32, sn: f32, first: bool) -> f32 { return select(own * cs + partner * sn, own * cs - partner * sn, first); } {%- endmacro %} {{ env.wgsl.resourceDeclarations }} // com.microsoft.SparseAttention, KV append pass. // past_key and present_key are the same allocation, so this writes only the new rows: // one invocation per (batch, kv head, new token, component) of the BNSH cache, landing // at absolute position past + t. Nothing rewrites the retained history. const KV_HEADS: u32 = {{ kvNumHeads }}u; const HEAD_DIM: u32 = {{ headSize }}u; const MAX_CACHE_SEQ: u32 = {{ maxCacheSeq }}u; {% if packedQkv %} // Packed layout: query carries [Q | K | V] on one row, so K starts after the query // heads and V after the key heads. const Q_STRIDE: u32 = {{ packedStride }}u; const PACKED_K_OFFSET: u32 = {{ numHeads * headSize }}u; const PACKED_V_OFFSET: u32 = {{ (numHeads + kvNumHeads) * headSize }}u; {% else %} const KV_HIDDEN: u32 = {{ kvNumHeads * headSize }}u; {% endif %} const WG: u32 = {{ appendWorkgroupSize }}u; {% if usesRotary %} const ROTARY_HALF: u32 = {{ rotaryHalf }}u; const ROTARY_DIM: u32 = {{ rotaryDim }}u; {% endif %} {{ sparse_schedule() }} {% if usesRotary %} {{ sparse_rotary(rotaryInterleaved) }} {% endif %} @compute @workgroup_size(WG, 1, 1) fn main(@builtin(global_invocation_id) gid: vec3) { // 2D-folded flat index: gid.y carries the high bits past the // per-axis dispatch fold width. Reduces to gid.x when the dispatch does not fold. let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG; let count = params.batchSize * KV_HEADS * params.seqLen * HEAD_DIM; if (index >= count) { return; } let d = index % HEAD_DIM; let t = (index / HEAD_DIM) % params.seqLen; let kv_head = (index / (HEAD_DIM * params.seqLen)) % KV_HEADS; let batch = index / (HEAD_DIM * params.seqLen * KV_HEADS); let position = past_sequence_length(batch) + t; let dst = ((batch * KV_HEADS + kv_head) * MAX_CACHE_SEQ + position) * HEAD_DIM + d; {% if packedQkv %} let row = (batch * params.seqLen + t) * Q_STRIDE; let k_base = row + PACKED_K_OFFSET + kv_head * HEAD_DIM; let v_base = row + PACKED_V_OFFSET + kv_head * HEAD_DIM; {% else %} let k_base = ((batch * params.seqLen + t) * KV_HIDDEN) + kv_head * HEAD_DIM; let v_base = k_base; {% endif %} {% if usesRotary %} // The key is rotated at its own absolute position before it enters the cache, so the // cache holds post-rotary keys and the attention pass never rotates again. if (d < ROTARY_DIM) { let slot = rotary_slot(d); let cs = f32(cos_cache[position * ROTARY_HALF + slot]); let sn = f32(sin_cache[position * ROTARY_HALF + slot]); let own = f32({{ kvSource }}[k_base + d]); let partner = f32({{ kvSource }}[k_base + rotary_partner(d)]); present_key[dst] = {{ scalar }}(rotary_value(own, partner, cs, sn, rotary_is_first(d))); } else { present_key[dst] = {{ scalar }}({{ kvSource }}[k_base + d]); } {% else %} present_key[dst] = {{ scalar }}({{ kvSource }}[k_base + d]); {% endif %} present_value[dst] = {{ scalar }}({{ vSource }}[v_base + d]); }