{% if usesF16 %} enable f16; {% endif %} {{ env.wgsl.resourceDeclarations }} // com.microsoft.EmbedLayerNormalization, embedding-sum pass. // sum = word_embedding[input_ids[t]] + position_embedding[p(t)] // (+ segment_embedding[segment_ids[t] or 0]) // One workgroup writes one token's row of the summed embedding into `output`, // where the normalization pass reads it back. The row bases -- which word, // position and segment rows this token gathers -- are the same for every element // of the row, so they are resolved once per workgroup. // The sum is materialized in the tensor type -- it is also the optional // `embedding_sum` output -- so the statistics the next pass computes describe // the values this op emits rather than an unobservable wider intermediate. const HIDDEN: u32 = {{ hidden }}u; {% if not hasPositionIds or broadcastPositionIds %} // Read only where the position row comes from the position within the sequence: // a per-token position_ids indexes directly instead. const SEQUENCE: u32 = {{ sequenceLength }}u; {% endif %} const WG: u32 = {{ workgroupSize }}u; // Defensive row clamps keep an invalid id from reading outside an embedding // table. Request validation rejects such ids before a valid dispatch. const WORD_ROWS: u32 = {{ wordRows }}u; const POSITION_ROWS: u32 = {{ positionRows }}u; {% if hasSegment and hasSegmentIds %} const SEGMENT_ROWS: u32 = {{ segmentRows }}u; {% endif %} @compute @workgroup_size(WG, 1, 1) fn main(@builtin(workgroup_id) wg: vec3, @builtin(num_workgroups) nwg: vec3, @builtin(local_invocation_id) lid: vec3) { // 2D-folded row index: wg.y carries the high bits past the // maxComputeWorkgroupsPerDimension dispatch limit. let token = wg.x + wg.y * nwg.x; if (token >= params.tokens) { return; } let tid = lid.x; let base = token * HIDDEN; let word_row = min(u32(max(input_ids[token], 0)), WORD_ROWS - 1u) * HIDDEN; {% if hasPositionIds %} {% if broadcastPositionIds %} // A position_ids of shape (1, sequence_length) is shared by every batch, so // the row index drops back to the position within the sequence. let position_id = position_ids[token % SEQUENCE]; {% else %} let position_id = position_ids[token]; {% endif %} let position_row = min(u32(max(position_id, 0)), POSITION_ROWS - 1u) * HIDDEN; {% else %} let position_row = min(token % SEQUENCE, POSITION_ROWS - 1u) * HIDDEN; {% endif %} {% if hasSegment %} {% if hasSegmentIds %} let segment_row = min(u32(max(segment_ids[token], 0)), SEGMENT_ROWS - 1u) * HIDDEN; {% else %} let segment_row = 0u; {% endif %} {% endif %} for (var i = tid; i < HIDDEN; i = i + WG) { // Materialize each addition in T; association is observable for f16. {% if hasSegment and scalar == "f16" %} var stored = {{ scalar }}(f32(word_embedding[word_row + i]) + f32(segment_embedding[segment_row + i])); stored = {{ scalar }}(f32(stored) + f32(position_embedding[position_row + i])); {% else %} var stored = {{ scalar }}(f32(word_embedding[word_row + i]) + f32(position_embedding[position_row + i])); {% if hasSegment %} stored = {{ scalar }}(f32(stored) + f32(segment_embedding[segment_row + i])); {% endif %} {% endif %} {% if writeEmbeddingSum %} embedding_sum[base + i] = stored; {% endif %} output[base + i] = stored; } }