File size: 7,314 Bytes
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 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | // Pass 3 of parallel DynamicQuantizeLinear: elementwise quantization using the
// read-only y_scale/y_zero_point produced by the finalize pass. Each workgroup
// covers the same WG * EPT contiguous chunk as the reduction pass, so their
// dispatch counts match. Quantization applies round-to-even and saturates the
// result to the uint8 range using the common scale and zero point.
{{ env.wgsl.resourceDeclarations }}
// ONNX DynamicQuantizeLinear uses correctly rounded f32 division followed by
// round-half-to-even. Every execution path uses these helpers so their numerical
// behavior cannot drift apart.
// WGSL permits f32 division to differ from the correctly-rounded result by
// 2.5 ULP, and fma() inherits separate multiply/add accuracy rather than
// promising a fused residual. Reconstruct the correctly-rounded normal result
// with integer significand division when the quotient can affect an integer
// rounding boundary. This is backend-independent and uses only exact u32 ops.
fn dynamic_quantize_exact_div_normal(numerator: f32, denominator: f32) -> f32 {
if (numerator == 0.0) {
return numerator;
}
let numerator_bits = bitcast<u32>(numerator);
let denominator_bits = bitcast<u32>(denominator);
let sign_bits = (numerator_bits ^ denominator_bits) & 0x80000000u;
let numerator_abs = numerator_bits & 0x7fffffffu;
let denominator_abs = denominator_bits & 0x7fffffffu;
if (denominator_abs == 0u
|| (numerator_abs & 0x7f800000u) == 0x7f800000u
|| (denominator_abs & 0x7f800000u) == 0x7f800000u) {
return numerator / denominator;
}
var numerator_mantissa = numerator_abs & 0x007fffffu;
var denominator_mantissa = denominator_abs & 0x007fffffu;
let numerator_biased_exponent = (numerator_abs >> 23u) & 0xffu;
let denominator_biased_exponent = (denominator_abs >> 23u) & 0xffu;
var numerator_exponent: i32;
var denominator_exponent: i32;
if (numerator_biased_exponent == 0u) {
numerator_exponent = -126;
// Zero returned above. A non-zero subnormal reaches the implicit-bit
// position in at most 23 exact shifts.
while ((numerator_mantissa & 0x00800000u) == 0u) {
numerator_mantissa = numerator_mantissa << 1u;
numerator_exponent = numerator_exponent - 1;
}
} else {
numerator_mantissa = numerator_mantissa | 0x00800000u;
numerator_exponent = i32(numerator_biased_exponent) - 127;
}
if (denominator_biased_exponent == 0u) {
denominator_exponent = -126;
while ((denominator_mantissa & 0x00800000u) == 0u) {
denominator_mantissa = denominator_mantissa << 1u;
denominator_exponent = denominator_exponent - 1;
}
} else {
denominator_mantissa = denominator_mantissa | 0x00800000u;
denominator_exponent = i32(denominator_biased_exponent) - 127;
}
var quotient_exponent = numerator_exponent - denominator_exponent;
var remainder = numerator_mantissa;
if (remainder < denominator_mantissa) {
remainder = remainder << 1u;
quotient_exponent = quotient_exponent - 1;
}
// The normalized ratio is now in [1, 2). Emit its implicit bit followed by
// all 23 stored significand bits using exact binary long division.
var quotient_mantissa = 0x00800000u;
remainder = remainder - denominator_mantissa;
for (var digit = 0u; digit < 23u; digit = digit + 1u) {
remainder = remainder << 1u;
if (remainder >= denominator_mantissa) {
remainder = remainder - denominator_mantissa;
quotient_mantissa = quotient_mantissa | (1u << (22u - digit));
}
}
// Round the 24-bit significand to nearest, ties to even. remainder and its
// doubled value are below 2^25, so no u32 overflow is possible.
let twice_remainder = remainder << 1u;
if (twice_remainder > denominator_mantissa
|| (twice_remainder == denominator_mantissa && (quotient_mantissa & 1u) != 0u)) {
quotient_mantissa = quotient_mantissa + 1u;
}
if (quotient_mantissa == 0x01000000u) {
quotient_mantissa = quotient_mantissa >> 1u;
quotient_exponent = quotient_exponent + 1;
}
let biased_exponent = quotient_exponent + 127;
if (biased_exponent <= 0 || biased_exponent >= 255) {
// DynamicQuantizeLinear's GPU-supported ranges are normal and finite.
// Preserve the implementation-defined subnormal/overflow behavior outside
// that contract; the integer quantizer never takes this branch near a
// half-integer boundary.
return numerator / denominator;
}
let result_bits = sign_bits
| (u32(biased_exponent) << 23u)
| (quotient_mantissa & 0x007fffffu);
return bitcast<f32>(result_bits);
}
fn dynamic_quantize_division_may_cross_half(estimate: f32) -> bool {
let lower = floor(estimate);
let fraction = estimate - lower;
let magnitude = abs(estimate);
let magnitude_bits = bitcast<u32>(magnitude);
let adjacent = bitcast<f32>(magnitude_bits + 1u);
let ulp = adjacent - magnitude;
// Division is allowed 2.5 ULP error. Eight ULP also covers the factor-of-two
// ULP change when an estimate straddles the 0.5 exponent boundary.
return abs(fraction - 0.5) <= ulp * 8.0;
}
fn round_dynamic_half_to_even(value: f32, scale: f32) -> i32 {
let estimate = value / scale;
var scaled = estimate;
// `select` evaluates both value operands in WGSL; use control flow so the
// 23-bit software divide remains a rare boundary fallback, not O(23) work
// for every quantized element.
if (dynamic_quantize_division_may_cross_half(estimate)) {
scaled = dynamic_quantize_exact_div_normal(value, scale);
}
let lower = floor(scaled);
let fraction = scaled - lower;
if (fraction < 0.5) {
return i32(lower);
}
if (fraction > 0.5) {
return i32(lower + 1.0);
}
let upper = lower + 1.0;
let half_lower = floor(lower * 0.5);
let lower_is_even = (lower - half_lower * 2.0) == 0.0;
return i32(select(upper, lower, lower_is_even));
}
const WG: u32 = {{ workgroupSize }}u;
{% if not vec4 %}
const EPT: u32 = {{ elemsPerThread }}u;
{% endif %}
@compute @workgroup_size(WG, 1, 1)
fn main(@builtin(workgroup_id) wg: vec3<u32>,
@builtin(local_invocation_id) lid: vec3<u32>,
@builtin(num_workgroups) nwg: vec3<u32>) {
let tid = lid.x;
let scale = y_scale[0];
let zp_i32 = i32(y_zero_point[0]);
// Fold the block grid across x/y at the device's per-axis workgroup limit.
// Per-element guards discard the over-dispatched tail.
let blk = wg.x + wg.y * nwg.x;
{% if vec4 %}
// The vec4 input load reads four scalars at once. Output storage still uses
// one u32 element for each quantized value.
let count4 = params.count / 4u;
let idx4 = blk * WG + tid;
if (idx4 < count4) {
let v = x[idx4];
let o = idx4 * 4u;
y[o + 0u] = u32(clamp(round_dynamic_half_to_even(v.x, scale) + zp_i32, 0, 255));
y[o + 1u] = u32(clamp(round_dynamic_half_to_even(v.y, scale) + zp_i32, 0, 255));
y[o + 2u] = u32(clamp(round_dynamic_half_to_even(v.z, scale) + zp_i32, 0, 255));
y[o + 3u] = u32(clamp(round_dynamic_half_to_even(v.w, scale) + zp_i32, 0, 255));
}
{% 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 q = clamp(round_dynamic_half_to_even(x[idx], scale) + zp_i32, 0, 255);
y[idx] = u32(q);
}
}
{% endif %}
}
|