{% set precastOperands = precastOperands | default(false) %} {% set precastScalar = precastScalar | default("f32") %} enable subgroups; {% if pinSubgroupSize32 %} enable subgroup_size_control; {% endif %} enable chromium_experimental_subgroup_matrix; diagnostic(off, chromium.subgroup_matrix_uniformity); {{ env.wgsl.resourceDeclarations }} // Rare-boundary fallback: reproduce the reference's binary64 scale division // and accumulator product using integer significands; no f64 feature required. fn quant_u96_add(a: vec3, b: vec3) -> vec3 { let low = a.x + b.x; let middle = a.y + b.y; let with_carry = middle + u32(low < a.x); return vec3(low, with_carry, a.z + b.z + u32(middle < a.y) + u32(with_carry < middle)); } fn quant_u96_shr(a: vec3) -> vec3 { return vec3((a.x >> 1u) | (a.y << 31u), (a.y >> 1u) | (a.z << 31u), a.z >> 1u); } fn quant_u96_round_shift(value: vec3, shift: u32) -> vec3 { var q = value; var sticky = false; var guard = false; for (var bit = 0u; bit < shift; bit++) { sticky = sticky || guard; guard = (q.x & 1u) != 0u; q = quant_u96_shr(q); } if (guard && (sticky || (q.x & 1u) != 0u)) { q = quant_u96_add(q, vec3(1u, 0u, 0u)); } return q; } fn quant_float_significand(bits: u32) -> u32 { return (bits & 0x7fffffu) | select(0x800000u, 0u, (bits & 0x7f800000u) == 0u); } fn quant_float_exponent(bits: u32) -> i32 { return max(1, i32((bits >> 23u) & 255u)) - 150; } fn quant_round_binary64(acc: i32, a_s: f32, b_s: f32, y_s: f32) -> i32 { if (acc == 0) { return 0; } let a_bits = bitcast(a_s); let b_bits = bitcast(b_s); let y_bits = bitcast(y_s); let a = quant_float_significand(a_bits); let b = quant_float_significand(b_bits); let denominator = quant_float_significand(y_bits); if (a == 0u || b == 0u) { return 0; } // The exact product of two 24-bit significands occupies at most 48 bits. let p0 = (a & 65535u) * (b & 65535u); let cross = (a >> 16u) * (b & 65535u) + (b >> 16u) * (a & 65535u); let product_low = p0 + (cross << 16u); let product_high = (a >> 16u) * (b >> 16u) + (cross >> 16u) + u32(product_low < p0); var input_bit = select(31 - i32(countLeadingZeros(product_low)), 63 - i32(countLeadingZeros(product_high)), product_high != 0u); var remainder = 0u; var significant_bits = 0u; var leading_exponent = 0; var significand = vec2(0u); // Long division generates the 53 bits of the binary64 multiplier. The // remainder fits in 25 bits, including its left shift. At most 24 leading // zero bits precede the first significant bit. loop { var incoming = 0u; if (input_bit >= 32) { incoming = (product_high >> u32(input_bit - 32)) & 1u; } else if (input_bit >= 0) { incoming = (product_low >> u32(input_bit)) & 1u; } remainder = (remainder << 1u) | incoming; let digit = u32(remainder >= denominator); if (digit != 0u) { remainder -= denominator; } if (significant_bits != 0u || digit != 0u) { if (significant_bits == 0u) { leading_exponent = input_bit; } significand = vec2((significand.x << 1u) | digit, (significand.y << 1u) | (significand.x >> 31u)); significant_bits++; } input_bit--; if (significant_bits == 53u) { break; } } let twice_remainder = remainder << 1u; if (twice_remainder > denominator || (twice_remainder == denominator && (significand.x & 1u) != 0u)) { let low = significand.x + 1u; significand = vec2(low, significand.y + u32(low == 0u)); } let scale_exponent = quant_float_exponent(a_bits) + quant_float_exponent(b_bits) - quant_float_exponent(y_bits) + leading_exponent - 52; // abs(INT_MIN) is representable as u32. Multiply the rounded multiplier's // significand by the exact accumulator into three little-endian u32 limbs. var magnitude = select(u32(acc), 0u - u32(acc), acc < 0); var operand = vec3(significand, 0u); var product = vec3(0u); loop { if ((magnitude & 1u) != 0u) { product = quant_u96_add(product, operand); } magnitude >>= 1u; if (magnitude == 0u) { break; } operand = vec3(operand.x << 1u, (operand.y << 1u) | (operand.x >> 31u), (operand.z << 1u) | (operand.y >> 31u)); } var product_top = 31 - i32(countLeadingZeros(product.x)); if (product.y != 0u) { product_top = 63 - i32(countLeadingZeros(product.y)); } if (product.z != 0u) { product_top = 95 - i32(countLeadingZeros(product.z)); } if (product_top + scale_exponent >= 10) { return select(1024, -1024, acc < 0); } if (product_top + scale_exponent < -2) { return 0; } let product_shift = u32(max(0, product_top - 52)); let rounded_product = quant_u96_round_shift(product, product_shift); let result_exponent = scale_exponent + i32(product_shift); var rounded = rounded_product; if (result_exponent < 0) { rounded = quant_u96_round_shift(rounded_product, u32(-result_exponent)); } else { rounded.x <<= u32(result_exponent); } return select(i32(rounded.x), -i32(rounded.x), acc < 0); } // A power-of-two division and an exactly represented <=48-bit product need // no binary64 boundary resolver. This includes ordinary dyadic quantization. fn quant_exact_dyadic_product(acc: i32, scale: f32) -> bool { let magnitude = select(u32(acc), 0u - u32(acc), acc < 0); let significand = quant_float_significand(bitcast(scale)); let integer_bits = 32u - countLeadingZeros(magnitude) - countTrailingZeros(magnitude); let scale_bits = 32u - countLeadingZeros(significand) - countTrailingZeros(significand); return integer_bits + scale_bits <= 48u; } fn quant_normal_expansion_range(v: f32) -> bool { let exponent = (bitcast(v) >> 23u) & 255u; // Reserve two binary32 significands plus four carry/error bits above the // minimum normal exponent, so residual arithmetic avoids FTZ underflow. return exponent >= (1u + 2u * 24u + 4u) && exponent < 255u; } fn round_expansion_ratio(numerator_hi: f32, numerator_lo: f32, denominator: f32, dyadic_scale: f32, dyadic_candidate: bool, acc: i32, a_s: f32, b_s: f32, y_s: f32) -> i32 { let estimate = numerator_hi / denominator; // Anything outside this range saturates after adding an 8-bit zero point; // returning early also keeps floor/i32 conversion away from Inf and overflow. if (estimate <= -1024.0) { return -1024; } if (estimate >= 1024.0) { return 1024; } let lo = floor(estimate); let hi = lo + 1.0; let midpoint = lo + 0.5; let midpoint_hi = fma(midpoint, denominator, 0.0); if ((bitcast(midpoint_hi) & 0x7f800000u) == 0x7f800000u) { return quant_round_binary64(acc, a_s, b_s, y_s); } let midpoint_lo = fma(midpoint, denominator, -midpoint_hi); // The high-word subtraction is exact for these similarly sized products // (Sterbenz); adding the residual difference therefore preserves its sign // even when the rounded high words straddle adjacent f32 values. let midpoint_delta = fma(1.0, numerator_hi - midpoint_hi, numerator_lo - midpoint_lo); // Escalate ambiguous expansion signs instead of changing the tie tolerance. // Six FMA/product tails and their additions fit within this conservative // 16*u^2 error envelope (u=2^-24), including binary64 reference rounding. if ((bitcast(midpoint_delta) & 0x7f800000u) == 0x7f800000u) { return quant_round_binary64(acc, a_s, b_s, y_s); } if (abs(midpoint_delta) <= max(abs(numerator_hi), abs(midpoint_hi)) * 0x1p-44) { if (!dyadic_candidate || !quant_exact_dyadic_product(acc, dyadic_scale)) { return quant_round_binary64(acc, a_s, b_s, y_s); } } if (midpoint_delta < 0.0) { return i32(lo); } if (midpoint_delta > 0.0) { return i32(hi); } let half_lo = floor(lo * 0.5); let lo_is_even = lo - half_lo * 2.0 == 0.0; return i32(select(hi, lo, lo_is_even)); } // Multiplication by a power of two keeps every i32 bit in binary64. Round the // exact dyadic value directly, including INT_MIN, half-even ties and saturation. fn quant_round_power_two(acc: i32, exponent: i32) -> i32 { let magnitude = select(u32(acc), 0u - u32(acc), acc < 0); var rounded = 0u; if (exponent >= 0) { if (exponent >= 10) { return select(1024, -1024, acc < 0); } rounded = min(magnitude, 1024u >> u32(exponent)) << u32(exponent); } else { let shift = u32(-exponent); if (shift >= 32u) { return 0; } let whole = magnitude >> shift; let remainder = magnitude & ((1u << shift) - 1u); let half = 1u << (shift - 1u); rounded = min(1024u, whole + u32(remainder > half || (remainder == half && (whole & 1u) != 0u))); } return select(i32(rounded), -i32(rounded), acc < 0); } fn round_requantized_precise(acc: i32, a_s: f32, b_s: f32, y_s: f32) -> i32 { if (acc == 0) { return 0; } let a_bits = bitcast(a_s); let b_bits = bitcast(b_s); let y_bits = bitcast(y_s); if (((a_bits | b_bits | y_bits) & 0x807fffffu) == 0u && min(a_bits, min(b_bits, y_bits)) != 0u && max(a_bits, max(b_bits, y_bits)) < 0x7f800000u) { let exponent = i32(a_bits >> 23u) + i32(b_bits >> 23u) - i32(y_bits >> 23u) - 127; return quant_round_power_two(acc, exponent); } let acc_f = f32(acc); // Every i32 high part aligned to 256 is exactly representable in f32. // Recover the conversion residual without an out-of-range i32(acc_f) // conversion when INT_MAX rounds up to 2^31. let acc_tail = fma(-1.0, acc_f - f32(acc & -256), f32(acc & 255)); // Equal b/y scales cancel exactly. Bypass their product and division so a // rounded intermediate cannot move a near-half value onto the tie. if (bitcast(b_s) == bitcast(y_s)) { let direct_hi = fma(acc_f, a_s, 0.0); let direct_lo = fma(acc_tail, a_s, fma(acc_f, a_s, -direct_hi)); if (!quant_normal_expansion_range(direct_hi)) { return quant_round_binary64(acc, a_s, b_s, y_s); } return round_expansion_ratio(direct_hi, direct_lo, 1.0, a_s, true, acc, a_s, b_s, y_s); } // Keep the scale product as a two-float expansion. Its FMA residual preserves // which side of a rounding midpoint a near-half value occupies. let ab_hi = fma(a_s, b_s, 0.0); let ab_lo = fma(a_s, b_s, -ab_hi); if (!quant_normal_expansion_range(ab_hi) || !quant_normal_expansion_range(y_s) || y_s > 0x1p126) { return quant_round_binary64(acc, a_s, b_s, y_s); } let product_hi = fma(acc_f, ab_hi, 0.0); if (!quant_normal_expansion_range(product_hi)) { return quant_round_binary64(acc, a_s, b_s, y_s); } let rounded_acc_tail = fma(acc_f, ab_hi, -product_hi) + fma(acc_f, ab_lo, 0.0); let product_tail = fma(acc_tail, ab_hi, rounded_acc_tail) + fma(acc_tail, ab_lo, 0.0); // Compare the expansion directly. Renormalizing it adds a cancelling // subtraction/addition chain that can erase the residual during lowering. let denominator_bits = bitcast(y_s); let denominator_power_two = (denominator_bits & 0x7fffffu) == 0u; let dyadic_candidate = denominator_power_two && ab_lo == 0.0; return round_expansion_ratio(product_hi, product_tail, y_s, ab_hi, dyadic_candidate, acc, a_s, b_s, y_s); } // A conservative binary32 error filter accepts only a result safely inside one // integer rounding bin. Ambiguous boundaries keep the full expansion/binary64 // path; this is an exact-result shortcut, not an approximate quantization mode. fn round_requantized(acc: i32, a_s: f32, b_s: f32, y_s: f32) -> i32 { if (acc == 0) { return 0; } let a_bits = bitcast(a_s); let b_bits = bitcast(b_s); let y_bits = bitcast(y_s); let ea = (a_bits >> 23u) & 255u; let eb = (b_bits >> 23u) & 255u; let ey = (y_bits >> 23u) & 255u; // Certify the range before arithmetic: overflow need not produce infinity. // Any nonzero i32 converts to a float with biased exponent <=158. These // scale-only bounds cover every accumulator and can be shared across lanes. // Reserve two significands plus carry bits above FTZ, and a factor of two // below overflow for products, division and the expansion midpoint product. if (((a_bits | b_bits | y_bits) & 0x80000000u) != 0u || min(ea, eb) == 0u || max(ea, eb) >= 255u || ea + eb < 180u || ea + eb > 347u || ey < 53u || ey > 242u || ea + eb > ey + 220u) { return quant_round_binary64(acc, a_s, b_s, y_s); } let scale = fma(a_s, b_s, 0.0); let product = fma(f32(acc), scale, 0.0); let estimate = product / y_s; if (estimate <= -1024.0) { return -1024; } if (estimate >= 1024.0) { return 1024; } let nearest = round(estimate); // 32 binary32 unit roundoffs reserve room for the i32 conversion, two // products, quotient, and distance arithmetic, plus binary64 rounding. let margin = max(1.0, abs(estimate)) * 0x1p-19; if (abs(estimate - nearest) < 0.5 - margin) { return i32(nearest); } {% if device.adapterInfo.vendor != "apple" %} // A rounded half-integer estimate can hide which side the exact f32-scale // ratio lies on. Resolve that one-ulp neighborhood conservatively outside // the compact fast path; exact power-of-two ratios already have a cheap // and correct path in round_requantized_precise. if (abs(abs(estimate - nearest) - 0.5) <= max(1.0, abs(estimate)) * 0x1p-24) { let dyadic_scales = ((a_bits | b_bits | y_bits) & 0x807fffffu) == 0u && min(a_bits, min(b_bits, y_bits)) != 0u && max(a_bits, max(b_bits, y_bits)) < 0x7f800000u; if (!dyadic_scales) { return quant_round_binary64(acc, a_s, b_s, y_s); } } {% endif %} return round_requantized_precise(acc, a_s, b_s, y_s); } // Adjusted byte integers fit exactly in f32. Each bounded f32 dot product // has absolute magnitude at most 2^24; int32 adds then preserve wraparound. const TILE_M: u32 = {{ sgmatTileM }}u; const TILE_N: u32 = {{ sgmatTileN }}u; const TILE_K: u32 = {{ sgmatTileK }}u; {% if precastOperands %} const PADDED_K: u32 = {{ preparedK }}u; const PADDED_N: u32 = {{ preparedN }}u; var matrix_out: array; {% else %} var tile_A: array; var tile_B: array; {% if not sgmatReuseStaging %} var scratch: array, 4>, {{ sgmatSubgroups }}>; {% endif %} {% endif %} @compute @workgroup_size({{ sgmatWorkgroup }}){{ " @subgroup_size(32)" if pinSubgroupSize32 else "" }} fn main(@builtin(workgroup_id) wid: vec3, {% if not precastOperands or not matrixSubgroupId %} @builtin(local_invocation_index) tid: u32, {% endif %} @builtin(subgroup_invocation_id) lane: u32, @builtin(subgroup_id) subgroup_index: u32) { let subgroup = subgroup_index; let base_A = (subgroup % {{ sgmatRowGroups }}u) * 16u; let base_B = (subgroup / {{ sgmatRowGroups }}u) * 32u; let row = lane / 4u; let col = (lane % 4u) * 2u; // Bound adjusted byte magnitudes from scalar zero points. A column-wise // B zero point keeps the full byte-difference bound for the whole tile. let max_a = {% if matrixZeroPerRow %}255u{% else %}u32(max(abs({{ 0 if aUnsigned else (0 - 128) }} - i32(a_zero_point[0])), abs({{ 255 if aUnsigned else 127 }} - i32(a_zero_point[0])))){% endif %}; let max_b = {% if hasBZero and not bZeroPerColumn %}u32(max(abs({{ 0 if bUnsigned else (0 - 128) }} - i32(b_zero_point[0])), abs({{ 255 if bUnsigned else 127 }} - i32(b_zero_point[0])))){% else %}{{ 255 if hasBZero or bUnsigned else 128 }}u{% endif %}; let partial_k = (16777216u / (max_a * max_b) / TILE_K) * TILE_K; var totals: array; for (var partial = 0u; partial < params.K; partial += partial_k) { {% for r in range(2) %}{% for c in range(4) %} var matC{{ r }}{{ c }}: subgroup_matrix_result; {% endfor %}{% endfor %} let partial_end = min(params.K, partial + partial_k); for (var kb = partial; kb < partial_end; kb += TILE_K) { {% if not precastOperands %} for (var index = tid; index < TILE_M * TILE_K; index += {{ sgmatWorkgroup }}u) { let m = wid.y * TILE_M + index / TILE_K; let k = kb + index % TILE_K; var value = f32(0.0); if (m < params.M && k < params.K) { value = f32(i32(a[m * params.K + k]) - i32(a_zero_point[{{ "m" if matrixZeroPerRow else "0" }}])); } tile_A[index] = value; } for (var index = tid; index < TILE_K * TILE_N; index += {{ sgmatWorkgroup }}u) { let k = kb + index / TILE_N; let n = wid.x * TILE_N + index % TILE_N; var value = f32(0.0); if (k < params.K && n < params.N) { value = f32(i32(b[{% if matrixBatchB %}wid.z * params.bBatchStride + {% endif %}k * params.N + n]) - i32(b_zero_point[{{ "n" if bZeroPerColumn else "0" }}])); } tile_B[index] = value; } workgroupBarrier(); {% endif %} for (var step = 0u; step < TILE_K; step += 8u) { {% for r in range(2) %} let matA{{ r }} = subgroupMatrixLoad, row_major>(&{{ "a" if precastOperands else "tile_A" }}, {% if precastOperands %}(wid.y * TILE_M + base_A + {{ r * 8 }}u) * PADDED_K + kb + step{% else %}(base_A + {{ r * 8 }}u) * TILE_K + step{% endif %}, {{ "PADDED_K" if precastOperands else "TILE_K" }}); {% endfor %} {% for c in range(4) %} let matB{{ c }} = subgroupMatrixLoad, row_major>(&{{ "b" if precastOperands else "tile_B" }}, {% if precastOperands %}{% if matrixBatchB %}wid.z * PADDED_K * PADDED_N + {% endif %}(kb + step) * PADDED_N + wid.x * TILE_N + base_B + {{ c * 8 }}u{% else %}step * TILE_N + base_B + {{ c * 8 }}u{% endif %}, {{ "PADDED_N" if precastOperands else "TILE_N" }}); {% endfor %} {% for r in range(2) %}{% for c in range(4) %} matC{{ r }}{{ c }} = subgroupMatrixMultiplyAccumulate(matA{{ r }}, matB{{ c }}, matC{{ r }}{{ c }}); {% endfor %}{% endfor %} } {% if not precastOperands %} workgroupBarrier(); {% endif %} } {% if precastOperands %} // The prepared operands need no staging barriers. Publish all eight // matrices together before converting the bounded partial to int32. {% for r in range(2) %}{% for c in range(4) %} subgroupMatrixStore(&matrix_out, subgroup * 512u + {{ r * 256 + c * 64 }}u, matC{{ r }}{{ c }}, 8u); {% endfor %}{% endfor %} workgroupBarrier(); {% for r in range(2) %}{% for c in range(4) %}{% for h in range(2) %} totals[{{ r * 8 + c * 2 + h }}] += i32(matrix_out[subgroup * 512u + {{ r * 256 + c * 64 }}u + row * 8u + col + {{ h }}u]); {% endfor %}{% endfor %}{% endfor %} workgroupBarrier(); {% else %} {% if sgmatReuseStaging %} // All staged A reads have completed. Reuse its storage for one result band // at a time: each subgroup needs 4 * 64 floats, exactly its A-tile share. {% endif %} {% for r in range(2) %} {% for c in range(4) %} subgroupMatrixStore({% if sgmatReuseStaging %}&tile_A{% else %}&scratch[subgroup][{{ c }}]{% endif %}, {% if sgmatReuseStaging %}subgroup * 256u + {{ c * 64 }}u{% else %}0u{% endif %}, matC{{ r }}{{ c }}, 8u); {% endfor %} workgroupBarrier(); {% for c in range(4) %}{% for h in range(2) %} totals[{{ r * 8 + c * 2 + h }}] += i32({% if sgmatReuseStaging %}tile_A[subgroup * 256u + {{ c * 64 }}u + row * 8u + col + {{ h }}u]{% else %}scratch[subgroup][{{ c }}][row * 8u + col + {{ h }}u]{% endif %}); {% endfor %}{% endfor %} workgroupBarrier(); {% endfor %} {% endif %} } {% for r in range(2) %}{% for c in range(4) %}{% for h in range(2) %} { let m = wid.y * TILE_M + base_A + {{ r * 8 }}u + row; let n = wid.x * TILE_N + base_B + {{ c * 8 + h }}u + col; if (m < params.M && n < params.N) { let acc = totals[{{ r * 8 + c * 2 + h }}]{% if matrixHasBias %} + bias[{{ "m" if matrixBiasPerRow else "n" }}]{% endif %}; let rounded = round_requantized(acc, f32(a_scale[0]), f32(b_scale[{{ "m" if matrixScalePerRow else ("n" if bScalePerColumn else "0") }}]), f32(y_scale[0])); let q = clamp(rounded + i32(y_zero_point[0]), {{ qMin }}, {{ qMax }}); y[{% if matrixBatchB %}wid.z * params.yBatchStride + {% endif %}m * params.N + n] = {{ "u32(q)" if yUnsigned else "q" }}; } } {% endfor %}{% endfor %}{% endfor %} }