File size: 6,325 Bytes
bbc3807
 
 
f430939
 
 
 
bbc3807
 
 
f430939
 
 
 
bbc3807
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163b53c
 
 
 
 
 
bbc3807
163b53c
 
bbc3807
 
 
 
 
 
 
 
 
 
 
f430939
bbc3807
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f430939
163b53c
 
 
f430939
 
 
163b53c
 
 
 
f430939
 
 
 
 
 
163b53c
 
 
 
 
 
 
f430939
 
 
 
 
 
 
 
 
 
 
 
bbc3807
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// Pass 1 of parallel DynamicQuantizeLinear. Regular mode reduces one contiguous
// WG * EPT block per workgroup. Grid-stride mode caps the partial count and has
// every lane revisit the tensor at grid-sized strides. Both modes use the same
// subgroup/tree combine and write one min/max pair per workgroup. The subgroup
// combine publishes one shared-memory slot per invocation (its subgroup pair
// from the elected lane, the neutral 0.0 from every other lane) and every
// subgroup folds all of them.
//
// The local min/max start at 0.0 because the ONNX DynamicQuantizeLinear range
// always includes zero; out-of-range lanes contribute the same neutral value.
// f32 min/max is order-independent, so subgroup and workgroup-tree combinations
// produce the same extrema. Without subgroups, a full workgroup tree combines
// the per-invocation partials.
{% set gridStride = gridStride if gridStride is defined else false %}
{% if useSubgroups %}
enable subgroups;
{% endif %}
{{ env.wgsl.resourceDeclarations }}

const WG: u32 = {{ workgroupSize }}u;
{% if not gridStride and not vec4 %}
const EPT: u32 = {{ elemsPerThread }}u;

{% endif %}
var<workgroup> wgMin: array<f32, WG>;
var<workgroup> wgMax: array<f32, WG>;

@compute @workgroup_size(WG, 1, 1)
fn main(@builtin(workgroup_id) wg: vec3<u32>,
        @builtin(local_invocation_id) lid: vec3<u32>{{ "," if gridStride or useSubgroups else "" }}
{% if gridStride %}
        @builtin(num_workgroups) nwg: vec3<u32>{{ "," if useSubgroups else "" }}
{% endif %}
{% if useSubgroups %}
        @builtin(subgroup_invocation_id) sgLane: u32,
        @builtin(subgroup_size) sgSize: u32
{% endif %}
) {
  let tid = lid.x;
{% if gridStride %}
  let blk = wg.x;
  // The capped x-only dispatch is the full logical grid. Each workgroup writes
  // exactly one partial, matching finalize's numPartials uniform.
  let gridThreads = nwg.x * WG;
{% else %}
  // 2D-folded block index: wg.y carries the high bits past the device's
  // per-dimension dispatch limit. The whole over-dispatched workgroup returns
  // before barriers or scratch stores.
  // blk/numBlocks are workgroup-uniform, so the return can't split the barrier.
  let blk = wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u;
{% if vec4 %}
  let numBlocks = (params.count / 4u + WG - 1u) / WG;
{% else %}
  let numBlocks = (params.count + WG * EPT - 1u) / (WG * EPT);
{% endif %}
  if (blk >= numBlocks) { return; }
{% endif %}
  var localMin = 0.0;
  var localMax = 0.0;
{% if gridStride %}
{% if vec4 %}
  // Adjacent lanes read adjacent vec4s on each grid-stride iteration.
  let count4 = params.count / 4u;
  for (var idx4 = blk * WG + tid; idx4 < count4; idx4 = idx4 + gridThreads) {
    let v = x[idx4];
    localMin = min(localMin, min(min(v.x, v.y), min(v.z, v.w)));
    localMax = max(localMax, max(max(v.x, v.y), max(v.z, v.w)));
  }
{% else %}
  for (var idx = blk * WG + tid; idx < params.count; idx = idx + gridThreads) {
    let value = x[idx];
    localMin = min(localMin, value);
    localMax = max(localMax, value);
  }
{% endif %}
{% else %}
{% if vec4 %}
  // Each thread reads one contiguous vec4; this path assumes params.count is
  // divisible by 4.
  let count4 = params.count / 4u;
  let idx4 = blk * WG + tid;
  if (idx4 < count4) {
    let v = x[idx4];
    localMin = min(localMin, min(min(v.x, v.y), min(v.z, v.w)));
    localMax = max(localMax, max(max(v.x, v.y), max(v.z, v.w)));
  }
{% else %}
  let base = blk * WG * EPT;
  for (var e = 0u; e < EPT; e = e + 1u) {
    let idx = base + e * WG + tid;
    if (idx < params.count) {
      let v = x[idx];
      localMin = min(localMin, v);
      localMax = max(localMax, v);
    }
  }
{% endif %}
{% endif %}
{% if useSubgroups %}
  let sgMin = subgroupMin(localMin);
  let sgMax = subgroupMax(localMax);
  // Cross-subgroup fold that assumes nothing about which invocations share a
  // subgroup or how many subgroups there are (it does require the uniform
  // control flow this entry point already has): every invocation owns the slot
  // at its own index, the elected lane
  // publishes its subgroup pair there and every other lane publishes 0.0, which
  // is an exact identity here because every lane's local range already includes
  // zero (so every published minimum is <= 0 and every maximum >= 0). Each
  // subgroup then folds all WG slots — lane `sgLane` walks slots sgLane,
  // sgLane + sgSize, ... — and one more collective merges the lane partials, so
  // every slot is merged exactly once at any legal width. min/max is commutative
  // and associative, so the fold order does not change the result.
  var totalMin = sgMin;
  var totalMax = sgMax;
  // A one-subgroup workgroup is already fully reduced by the collectives above.
  // The test reads the `subgroup_size` builtin, which is uniform; a collective's
  // result is not uniform to WGSL's analysis and may not guard a barrier.
  if (sgSize != WG) {
    // The coordinates are the BUILTINS, never `subgroupExclusiveAdd(1u)` /
    // `subgroupAdd(1u)`: those agree with them under this contract, but a driver
    // in the wild answers a claim derived from them with zero and leaves most of
    // the workgroup's slots unclaimed. Every lane of this entry point is active
    // here, so the full subgroup width is the right stride.
    let rank = sgLane;
    let count = sgSize;
    let leader = rank == 0u;
    wgMin[tid] = select(0.0, sgMin, leader);
    wgMax[tid] = select(0.0, sgMax, leader);
    workgroupBarrier();
    var foldMin = 0.0;
    var foldMax = 0.0;
    for (var i = rank; i < WG; i = i + count) {
      foldMin = min(foldMin, wgMin[i]);
      foldMax = max(foldMax, wgMax[i]);
    }
    totalMin = subgroupMin(foldMin);
    totalMax = subgroupMax(foldMax);
  }
  if (tid == 0u) {
    partial_min[blk] = totalMin;
    partial_max[blk] = totalMax;
  }
{% else %}
  // No-subgroups fallback: full workgroup-tree min/max over per-thread partials.
  wgMin[tid] = localMin;
  wgMax[tid] = localMax;
  workgroupBarrier();
  for (var step = WG >> 1u; step > 0u; step = step >> 1u) {
    if (tid < step) {
      wgMin[tid] = min(wgMin[tid], wgMin[tid + step]);
      wgMax[tid] = max(wgMax[tid], wgMax[tid + step]);
    }
    workgroupBarrier();
  }
  if (tid == 0u) {
    partial_min[blk] = wgMin[0];
    partial_max[blk] = wgMax[0];
  }
{% endif %}
}