File size: 3,339 Bytes
5b47652
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
{% 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<u32>,
        @builtin(num_workgroups) nwg: vec3<u32>,
        @builtin(local_invocation_id) lid: vec3<u32>) {
  // 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;
  }
}