tileref

Host reference implementations of the Tile IR kernels: for each kernel, a pure function that answers what the device's output buffer should hold.

use std/gpu.{with_gpu_fake, last_out}
use tileref/ref.{relu_ref}

Each function takes the formats (dtypes) and contents (ins) of every argument buffer, the output buffer last, and answers what the output buffer holds after the kernel has run. A kernel that masks the lanes past n leaves them as they were, so each reference takes that n and copies the output's own tail past it. Fixing n and any constants gives the function the fake device wants (examples/projects/gpu_fake).

The package depends on std alone, never on tileir: a reference is the second opinion on a kernel and must not share its code path. The format arithmetic (round_to, wrap_*, nibble_*) is std/gpu's, so a reference and the fake device agree on what a format can hold.

Changes between versions: CHANGELOG.md.

API

tileref/ref

Host reference implementations for the Tile IR kernels: for each kernel scripts/tile-golden records, a plain function of the argument buffers' formats and contents that answers what the device's output buffer holds afterwards. scripts/tile-gpu-diff compares the device against these, and scripts/leetgpu-diff/problems.txt names one per leetgpu problem.

ref.LN2

const LN2: Float

The natural logarithm of 2, to the nearest Float.

ref.TWO_OVER_SQRT_PI

const TWO_OVER_SQRT_PI: Float

Two over the square root of pi, to the nearest Float: the series below and Abramowitz-Stegun 7.1.26 both carry it.

ref.PI

const PI: Float

Pi to the nearest Float, and pi over two split into a high part whose low 21 bits are zero and a remainder. The split is Cody and Waite's: k * PIO2_HI is exact for every |k| this file meets, so the reduced argument (x - k * PIO2_HI) - k * PIO2_LO keeps its leading bits where a single-word x - k * (pi / 2) would have lost them to cancellation.

ref.INV_SQRT2

const INV_SQRT2: Float

One over the square root of two, to the nearest Float. leetgpu 65 writes x / sqrt(2); the kernel and this reference both multiply by the reciprocal, which differs from the division in the last bit and is far inside the tier either way.

ref.masked_vadd_ref

fn masked_vadd_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

Element-wise sum of the first two buffers into the third, over the first n lanes: leetgpu 1 (Vector Addition) and 8 (Matrix Addition).

ref.masked_copy_ref

fn masked_copy_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The first buffer into the second, over the first n lanes: leetgpu 31 (Matrix Copy).

ref.relu_ref

fn relu_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

max(x, 0) over the first n lanes: leetgpu 21 (ReLU). The kernel is maxf with neither propagate_nan nor flush_to_zero, whose answer on a NaN operand is the other operand and on (-0.0, +0.0) is either zero; this answers +0.0 for both, so a corpus that exercised them would be asserting something this release has not pinned.

ref.leaky_relu_ref

fn leaky_relu_ref(n: Int, alpha: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

x where x >= 0 and alpha * x where x < 0, over the first n lanes: leetgpu 23 (Leaky ReLU). The kernel is an ordered lt and a select, so NaN and -0.0 both answer themselves here as they do there.

ref.clip_ref

fn clip_ref(n: Int, lo: Float, hi: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

min(max(x, lo), hi) over the first n lanes: leetgpu 62 (Value Clipping). maxf and minf again, so the NaN and signed-zero caveat of relu_ref holds.

ref.ref_exp

fn ref_exp(x: Float) -> Float

e raised to the power x. Range reduction to x = k * ln2 + r with |r| <= ln2 / 2, then the Taylor series of exp(r), then 2^k by exact doubling. Answers 0.0 where the result underflows and the infinity where it overflows.

ref.ref_log

fn ref_log(x: Float) -> Float

The natural logarithm of x. x = m * 2^e with m in [1, 2), then the atanh series of log(m), which converges fastest where the argument is near 1. Answers NaN for a negative argument and the negative infinity for zero.

ref.ref_sqrt

fn ref_sqrt(x: Float) -> Float

The square root of x, by Newton's iteration from a crude guess on x = m * 4^k with m in [1, 4). Eight iterations from within a factor of two converge to the last bit or two. Answers NaN for a negative argument.

ref.ref_tanh

fn ref_tanh(x: Float) -> Float

The hyperbolic tangent of x. Small arguments go through the series for exp(y) - 1 rather than through ref_exp, because (e - 1) / (e + 1) loses every significant digit of a small answer to the subtraction; past |x| = 20 the answer is 1 to the last bit of a Float. This is the second opinion for the device's tanh, which is a hardware function, and for the tanh approximation of the gelu that leetgpu 74's feed-forward asks for.

ref.ref_erf

fn ref_erf(x: Float) -> Float

The error function of x, by the series erf(x) = (2 / sqrt(pi)) * exp(-x^2) * sum 2^n x^(2n+1) / (1*3*..*(2n+1)). Every term of that sum is positive, so nothing cancels and the sum keeps its relative accuracy however large x^2 makes it grow; the exp(-x^2) in front brings it back. Past |x| = 6 the answer is 1 to the last bit of a Float (erfc(6) is 2.2e-17) and this answers exactly 1 there. Accurate to about 4e-15 over [-8, 8], measured against a correctly rounded erf.

This is the SECOND OPINION for the device composition in packages/tileir/src/dev.dawn, which is Abramowitz-Stegun 7.1.26 and has an absolute error of 1.5e-7. A reference running the same five-term rational would agree with a WRONG one, and would also leave the approximation's error unmeasurable; against this series the difference the device shows is 7.1.26's error and nothing else.

ref.ref_sin

fn ref_sin(x: Float) -> Float

The sine of x. Range reduction to a quadrant and a remainder in [-pi/4, pi/4], then the Taylor series of that quadrant's function. This is the SECOND OPINION for the device's sin, which is a libdevice approximation; the two are compared against each other and neither is the definition of the other.

ref.ref_cos

fn ref_cos(x: Float) -> Float

The cosine of x, by the same reduction one quadrant along.

ref.ref_tan

fn ref_tan(x: Float) -> Float

The tangent of x, as the quotient of the two above. Near a pole the denominator is small and the answer is large, and the relative part of the tolerance tier is what carries the comparison there; a corpus is not allowed to sit ON a pole, where both sides answer different infinities.

ref.ref_sinh

fn ref_sinh(x: Float) -> Float

The hyperbolic sine of x. Small arguments go through the series, for ref_tanh's reason: (e - 1/e) / 2 loses every significant digit of a small answer to the subtraction. Past that it is the definition, with 1/e underflowing to zero on its own where e is enormous.

ref.ref_cosh

fn ref_cosh(x: Float) -> Float

The hyperbolic cosine of x. No cancellation anywhere: both halves of (e + 1/e) / 2 are positive, so the definition is accurate over the whole range and there is no small-argument branch.

ref.ref_atan

fn ref_atan(x: Float) -> Float

The arc tangent of x, in [-pi/2, pi/2]. Two reductions bring the argument inside 2 - sqrt(3): atan(x) = pi/2 - atan(1/x) for |x| > 1 and atan(x) = pi/6 + atan((x sqrt3 - 1) / (x + sqrt3)) for what is left above the pivot.

ref.ref_atan2

fn ref_atan2(y: Float, x: Float) -> Float

The atan2 of y over x, with the quadrant taken from the signs of both. THE NUMERATOR IS FIRST, which is the order packages/tileir/src/dev.atan2 takes, the order the dialect's own sentence gives ("the arc tangent of the ratio of first and second input arguments x / y", its names swapped) and C's atan2(y, x). The parameters were (a, b) until 2026-10-01 (#210), which hid that order from a caller naming them.

The axes are the special cases and they are where a corpus earns its keep: x < 0 moves the answer by a whole pi and a reference that forgot it would agree with the device on the right half plane and nowhere else. Signed zeros are not distinguished here: 0.0 is read as +0.0, so atan2(0, -1) is pi and not -pi, and the corpus in scripts/tile-gpu-diff carries no negative zero.

ref.ref_remf

fn ref_remf(a: Float, b: Float) -> Float

The floating-point remainder of a by b under TRUNCATED division: a - trunc(a / b) * b, which takes the sign of the dividend. This is C's fmod and not C's remainder, and the dialect's remf is stated as the first (Ops.td, RemFOp); which one the DEVICE runs is measured in scripts/tile-gpu-diff and not assumed here.

Unlike its neighbours above this one is EXACT, and it is exact by construction rather than by accuracy: the remainder of two representable numbers is representable, and shifting the divisor by powers of two and subtracting where it fits computes it with no rounding at all. So a disagreement with the device is a disagreement about the OPERATION and never about the last bit.

ref.reduce_sum_ref

fn reduce_sum_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The sum of the first n elements of the first buffer, at element 0 of the second: leetgpu 4 (Reduction).

ref.softmax_ref

fn softmax_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

exp(x - max(x)) / sum(exp(x - max(x))) over the first n lanes: leetgpu 5 (Softmax). The maximum is subtracted here as it is in the kernel; a reference that did not would still agree on a small corpus, which is why scripts/tile-gpu-diff's softmax-no-max-subtract mutant uses a corpus of large values.

ref.dot_ref

fn dot_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

sum(a * b) over the first n lanes, at element 0 of the third buffer: leetgpu 17 (Dot Product).

ref.mse_ref

fn mse_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

mean((p - t)^2) over the first n lanes: leetgpu 27 (Mean Squared Error). The division by n is one rounded operation on both sides, so it costs the comparison nothing.

ref.monte_carlo_ref

fn monte_carlo_ref(n: Int, scale: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

scale * sum(y) over the first n lanes: leetgpu 35 (Monte Carlo Integration), where scale is (b - a) / n, the constant the kernel was recorded with.

ref.rms_norm_ref

fn rms_norm_ref(n: Int, eps: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

x / sqrt(mean(x^2) + eps) over the first n lanes: leetgpu 50 (RMS Normalization).

ref.silu_ref

fn silu_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

x / (1 + exp(-x)) over the first n lanes: leetgpu 52 (Sigmoid Linear Unit).

ref.sigmoid_ref

fn sigmoid_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

1 / (1 + exp(-x)) over the first n lanes: leetgpu 68 (Sigmoid Activation).

ref.ppo_loss_ref

fn ppo_loss_ref(n: Int, eps: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

-mean(min(r * A, clip(r, 1 - eps, 1 + eps) * A)) with r = exp(new - old), over the first n lanes: leetgpu 107 (PPO Clipped Surrogate Loss). ins is the new log probabilities, the old ones, the advantages and the output buffer.

ref.dpo_loss_ref

fn dpo_loss_ref(n: Int, beta: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

-mean(log(sigmoid(beta * ((pc - pr) - (rc - rr))))) over the first n lanes: leetgpu 108 (DPO Sequence Loss). ins is the policy's chosen and rejected log probabilities, the reference model's two, and the output buffer.

ref.matmul_ref

fn matmul_ref(m: Int, k: Int, n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

c = a * b for an m by k matrix and a k by n one, both row-major: leetgpu 2 (Matrix Multiplication). ins is [a, b, old c].

ref.batched_matmul_ref

fn batched_matmul_ref(batch: Int, m: Int, k: Int, n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

batch independent m by k times k by n products, the batches laid out one after another: leetgpu 30 (Batched Matrix Multiplication).

ref.view_padding_ref

fn view_padding_ref(n: Int, out_n: Int, pad: Float, x: List[Float], old: List[Float]) -> List[Float]

What a partition_view load answers when the tile runs past the end of the tensor and the store that follows is into a view wide enough to keep it: the first n lanes are the input's, the lanes from there to out_n are the view's padding value, and anything past out_n is what the output buffer already held. Knife T11's oracle for the PaddingValue enum.

x and old are BIT PATTERNS and not numbers: scripts/tile-gpu-diff's view_padding runs an f32 kernel over i32 buffers, so what the host compares is the exact pattern each padding value put on the device, which is the only way zero and neg_zero are two answers rather than one. pad is that pattern for this output.

ref.strided_pad_ref

fn strided_pad_ref(n: Int, tile: Int, trav: Int, pad: Float, x: List[Float], old: List[Float]) -> List[Float]

The tile a STRIDED view reads at grid index b, laid end to end: element b * tile + j of the answer is element b * trav + j of x where that is inside the tensor, and pad where it is not. scripts/tile-gpu-diff's view_stride_pad (knife T13).

The traversal is what makes this different from a partition view's reference: the grid steps trav elements and the tile covers tile of them, so with trav the larger the reader SKIPS and with trav the smaller it reads each element more than once. The output has one tile per grid index, and the index space rounds up, which is why the last tile can run past the end of the tensor and the padding value is visible at all.

ref.gather_pad_ref

fn gather_pad_ref(rows: Int, cols: Int, gcols: Int, pad: Float, ids: List[Float], x: List[Float], old: List[Float]) -> List[Float]

The tile a GATHER/SCATTER view reads: row r of the answer is row ids[r] of a rows by cols row-major tensor, widened to gcols columns with pad in the columns past the end of the tensor. scripts/tile-gpu-diff's view_gather_pad (knife T13).

The row numbers come from the kernel at run time and the columns do not: that asymmetry IS the gather view, and the padding here is on the column side because a row the tensor does not have is a different question from a column it does not have.

ref.view_atomic_bf16_ref

fn view_atomic_bf16_ref(n: Int, bins: Int, tile: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The bin counts view_atomic_bf16 reduces into a BF16 view (knife TA). ins is [x, out] and the answer rewrites the second buffer's first bins lanes, leaving the rest of it alone.

This is view_atomic_ref's add output and nothing else, at another element format. The kernel exists because atomic_red_view_tko's addf over BF16 is the one thing in the view family that Ops.td gates on an architecture, so what is being asked is whether a card above that gate answers the same counts a card below it cannot be asked for at all.

NO ROUNDING APPEARS HERE, and that is a property of the corpus rather than of bf16. Eight tile blocks reduce into each bin in no particular order, so an inexact sum would depend on the order they arrived in; bf16 carries eight significand bits, whole numbers up to 256 are exact in it, and scripts/tile-gpu-diff/arch_diff.dawn holds the largest bin count below that.

ref.view_atomic_ref

fn view_atomic_ref(n: Int, bins: Int, tile: Int, bias: Int, dtypes: List[String], ins: List[List[Float]]) -> List[(Int, List[Float])]

The nine reductions atomic_red_view_tko can make, each over the SAME per-block contribution: scripts/tile-gpu-diff's view_atomic (knife T13). ins is [x, and the nine output buffers as the launch found them] and the answer rewrites positions 1 to 9.

The contribution of block b to bin j is how many of its tile lanes hold the value j, counting only lanes below n. Summed over the blocks that is exactly histogram_ref, which is why the add output can share that reference; the other eight fold the same counts LESS bias, and the bias is what puts negative numbers in so that umax and umin are not max and min spelled twice.

Every fold here is exact whatever order the blocks arrive in, which is the whole point of an atomic reduction: the eight integer modes are associative and commutative over i32, and the ninth adds small integers in binary64.

ref.masked_double_ref

fn masked_double_ref(limit: Int, x: List[Float], old: List[Float]) -> List[Float]

The first limit lanes of x doubled, with every lane from limit on kept from old: scripts/tile-gpu-diff's view_tensor_shape (knife T12).

limit is not a number the kernel was recorded with. It is what get_tensor_shape answered on the device for a tensor whose extents are ?, and the kernel uses it as the bound of the mask on both its load and its store, so a wrong answer shows up as lanes that were doubled and should not have been, or the other way about.

ref.view_grid_sum_ref

fn view_grid_sum_ref(rows: Int, cols: Int, tile: Int, x: List[Float], old: List[Float]) -> List[Float]

One tile by tile tile holding the sum of EVERY tile of a rows by cols row-major tensor: lane (p, q) is the sum over the grid of the element at row i * tile + p and column j * tile + q, counting zero where that falls outside the tensor. scripts/tile-gpu-diff's view_index_space (knife T12).

The grid is ceil(rows / tile) by ceil(cols / tile), which is exactly what get_index_space_shape answers on the device and what the kernel uses as its two loop bounds. The zero outside the tensor is the partition view's padding_value, so this reference is the padding rule and the index space rule at once.

ref.transpose_ref

fn transpose_ref(rows: Int, cols: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The transpose of a rows by cols row-major matrix, as a cols by rows one. No leetgpu problem: this is the oracle for a store whose strides are not row-major.

ref.layer_norm_ref

fn layer_norm_ref(rows: Int, d: Int, eps: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

(x - mean) / sqrt(var + eps) * gamma + beta per ROW of a rows by d matrix: leetgpu 113 (Layer Normalization). ins is [x, gamma, beta, old], with gamma and beta d long.

ref.batch_norm_ref

fn batch_norm_ref(rows: Int, channels: Int, eps: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The same normalisation per CHANNEL of a rows by channels matrix, over the column: leetgpu 40 (Batch Normalization). gamma and beta are channels long.

ref.group_norm_ref

fn group_norm_ref(n: Int, c: Int, hw: Int, groups: Int, eps: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The same normalisation over each (sample, group) of an n by c by hw tensor split into groups groups of channels: leetgpu 105 (Group Normalization). gamma and beta are c long, one per channel.

ref.fused_rms_norm_ref

fn fused_rms_norm_ref(rows: Int, d: Int, eps: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

(x + residual) / sqrt(mean((x + residual)^2) + eps) * w per ROW of a rows by d matrix: leetgpu 83 (Fused Residual Add + RMS Normalization). ins is [x, residual, w, old], with w d long.

ref.conv1d_ref

fn conv1d_ref(n: Int, k: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

out[i] = sum(x[i + t] * w[t]) over k taps, for the first n lanes: leetgpu 9 (1D Convolution), valid mode, so n is len(x) - k + 1. ins is [x, w, old]. The taps are folded in increasing t, which is the order the kernel unrolls them in.

ref.conv2d_ref

fn conv2d_ref(h: Int, w: Int, k: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The same in two dimensions over an h by w image and a k by k kernel, valid mode: leetgpu 10 (2D Convolution). The output is (h - k + 1) by (w - k + 1), and the taps are folded in row-major order over the kernel, which is the order the two host loops unroll in.

ref.max_pool_ref

fn max_pool_ref(h: Int, w: Int, k: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The largest element of each k by k window of an h by w image, the windows k apart in both directions: leetgpu 42 (Max Pooling), with the stride equal to the window and no padding, so the output is h / k by w / k. ins is [x, old]. The fold starts from the negative infinity the kernel's accumulator does, and takes the later element only when it is strictly larger, which is what maxf answers on a tie.

ref.interleave_ref

fn interleave_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

out[2i] = a[i] and out[2i + 1] = b[i] for the first n pairs: leetgpu 63 (Interleave). ins is [a, b, old].

ref.rgb_gray_ref

fn rgb_gray_ref(n: Int, wr: Float, wg: Float, wb: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The luminance of the first n pixels of an interleaved RGB buffer: leetgpu 66 (RGB to Grayscale). ins is [x, old], x three elements a pixel. Red and green are summed first and blue added to that, which is how the kernel brackets it.

ref.jacobi_ref

fn jacobi_ref(h: Int, w: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

One sweep of the five-point stencil over an h by w grid: leetgpu 69 (Jacobi Stencil). An interior point becomes a quarter of the sum of its four neighbours, summed up, down, left, right in that order; a boundary point keeps its value. ins is [x, old].

ref.depthwise_conv1d_ref

fn depthwise_conv1d_ref(c: Int, l: Int, k: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

c independent 1D convolutions, one a channel, each with its own k taps: leetgpu 90 (Depthwise 1D Convolution). x is c by l, w is c by k, and the output is c by l - k + 1. ins is [x, w, old].

ref.gaussian_blur_ref

fn gaussian_blur_ref(h: Int, w: Int, k: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

A k by k convolution over an h by w image that keeps the image's size, everything outside it counting as zero: leetgpu 28 (Gaussian Blur), where the weights are the ones the caller uploads. ins is [x, f, old]. A tap that reaches outside contributes 0.0 * f[t] rather than being skipped, which is what the kernel's padded load leaves in that lane.

ref.count_eq_ref

fn count_eq_ref(n: Int, target: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The number of the first n elements of the first buffer that equal target, at element 0 of the second: leetgpu 43 (Count Array Element) and its 2D and 3D shapes, 44 and 45, which are the same count over a buffer the caller reads with more indices.

The kernel counts by widening the comparison mask with exti and folding with addi; a widening that sign-extended would count -1 a lane and answer the negated count, which is what makes exti's UNSIGNED signedness a claim and not a detail.

ref.subarray_sum_ref

fn subarray_sum_ref(lo: Int, hi: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The wrapping sum of the elements of the first buffer whose index is in [lo, hi), at element 0 of the second: leetgpu 47 (Subarray Sum). The sum wraps modulo 2^32, as the device's addi does, so a corpus whose total leaves the i32 range is part of the claim rather than a hazard.

ref.rainbow_round

fn rainbow_round(h0: Int, k1: Int, k2: Int, k3: Int) -> Int

One round of the hash chain rainbow_ref and the rainbow kernel share: multiply, xor with a LOGICAL right shift, add, xor with an ARITHMETIC one, multiply, xor with a logical one. The two kinds of shift differ on every negative value and nowhere else, which is the whole of what scripts/tile-gpu-diff's shri-always-logical mutant moves.

ref.rainbow_ref

fn rainbow_ref(n: Int, rounds: Int, k1: Int, k2: Int, k3: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

rounds applications of rainbow_round to each of the first n elements of the first buffer, into the second: leetgpu 24 (Rainbow Table), whose chain is a hash applied over and over. The constants are the caller's, fixed when the kernel was recorded.

ref.int_ops_ref

fn int_ops_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

Every integer operation and every conversion the package can emit, folded into one lane: the reference for the int_ops coverage kernel, as elemops and mathops are for the float families. ins is [a, b, old].

The divisor is b | 1, so it is odd and never zero; the corpus still owes the kernel two things the device leaves undefined and this does not: no lane of a may be -2^31 (its divi by a b | 1 of -1 would overflow, and so would absi and negi in a way this reference cannot follow), and the shift counts are host constants inside [0, 32).

ref.sum_diff_ref

fn sum_diff_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[(Int, List[Float])]

Element-wise sum into the third buffer AND element-wise difference into the fourth, over the first n lanes: the multi-output shape, and the reference that a device writing only its last argument cannot satisfy. ins is [a, b, old sum, old diff]; each output keeps its own tail past n, as every masked reference does.

ref.reverse_ref

fn reverse_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[(Int, List[Float])]

The first n elements of the ONE buffer, reversed IN PLACE: leetgpu 19 (Reverse Array). ins is [x] and the answer rewrites position 0, which is the position it read.

n must be EVEN and is, in every recording: the kernel pairs lane i with lane n - 1 - i and covers the first n / 2 of them, so an odd n would leave the middle element to a block that does not exist. Anything past n is the buffer's own tail, kept, as under a mask.

ref.invert_ref

fn invert_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[(Int, List[Float])]

255 - x over the first n elements of the ONE buffer, IN PLACE: leetgpu 7 (Color Inversion) over a u8 image. ins is [img] and the answer rewrites position 0.

The subtraction is written on the values a u8 buffer holds, which are in [0, 255], so the answer is in [0, 255] and round_to has nothing to wrap. That is the whole reason the host format is U8 and not I8: on an i8 buffer the same bytes read as [-128, 127] and 255 - x would be a different number in both places at once.

ref.f16_ops_ref

fn f16_ops_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

((a + b) * a) - b over the first n lanes, ROUNDED AFTER EACH STEP to the output buffer's format: the f16 twin of int_ops, and the kernel that holds addf, mulf and subf at that width to the bit.

The three roundings are not decoration. The device's tile is f16 and every operation lands its result there, so a reference that computed the whole expression in Float and rounded once would answer a different number wherever an intermediate falls off the f16 grid. Each step here is narrow.round_f16 of an exactly computed operation, which by the double-rounding theorem is what the device's addf / mulf / subf with rounding<nearest_even> answer, bit for bit (the theorem covers + - * / and sqrt, and none of those three is outside it).

ref.dot_f16_ref

fn dot_f16_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The dot product of the first n lanes of two f16 buffers, accumulated at the OUTPUT buffer's width: leetgpu 58 (F16 Dot Product). ins is [a, b, old out] and the answer is element 0 of the output, the rest of it kept.

The kernel widens each operand with ftof before multiplying, so the products and the sum are the output format's and not f16's. The fold here is left to right and the device's is unspecified; the corpus is what makes the two agree (see wide_diff).

ref.token_embed_ref

fn token_embed_ref(n: Int, rows: Int, dim: Int, miss: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

Row i of the answer is row ids[i] of table, and miss where that id is outside [0, rows): leetgpu 106 (Token Embedding). ins is [ids, table, old out]; n is how many ids the launch covered and dim the width of a row.

An id outside the table is a defined answer here, not a lane to leave alone: the kernel masks its GATHER and not its store. Which is what gives the mask something an output buffer can show, and the reason scripts/tile-gpu-diff's corpus carries such ids.

ref.sort_ref

fn sort_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The first n values of the first buffer in ascending order, in the second: leetgpu 15 (Sorting). ins is [x, old out].

This is a SORT, not the kernel's rank-and-scatter written twice: the device computes where each value goes and writes it there, and this grows an ordered list by insertion. Equal values are interchangeable in the answer, so the kernel's tie-break (which keeps its ranks distinct, so that no two lanes scatter to one element) does not have to be repeated here to agree with it.

ref.merge_ref

fn merge_ref(m: Int, n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The first m values of the first buffer and the first n of the second, both already ascending, merged into the third: leetgpu 71 (Parallel Merge). ins is [a, b, old out].

A two-pointer merge, which is the algorithm the kernel does NOT use: the device computes each value's rank against the other run and scatters it there. Ties go to a, matching the kernel's strict comparison on one side and loose one on the other, which is also what keeps the two rank sets disjoint.

ref.scatter_perm_ref

fn scatter_perm_ref(lanes: Int, n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

out[perm[i]] = x[i] for every lane whose destination is inside [0, n), and nothing at all for the others: the scatter twin of int_ops_ref, with no leetgpu problem behind it. ins is [perm, x, old out], and lanes is how many lanes the launch covered.

The caller must hand this a perm whose in-range entries are DISTINCT. A scatter that names one element twice has no answer on the device (the dialect does not order the lanes of one store against each other), so a reference that answered anything for it would be inventing a rule.

ref.prefix_sum_ref

fn prefix_sum_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The inclusive prefix sum of the first n lanes of the first buffer, into the second: leetgpu 16 (Prefix Sum). ins is [x, old out], and the lanes past n keep what they held.

ref.max_subarray_ref

fn max_subarray_ref(n: Int, w: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The largest sum of w consecutive of the first n elements of the first buffer, at element 0 of the second: leetgpu 51 (Max Subarray Sum). ins is [x, old out].

A direct sum over each window, which is NOT what the kernel computes (it subtracts two prefix sums). Both are exact in the integers modulo 2^32, which is what makes this an independent opinion rather than the same arithmetic written twice.

ref.seg_scan_ref

fn seg_scan_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The exclusive prefix sum WITHIN each segment of the first n lanes, into the third buffer: leetgpu 70 (Segmented Exclusive Prefix Sum). ins is [values, flags, old out]; a segment begins at every lane whose flag is not zero, and lane 0's flag must be one of them.

Written the way leetgpu's own reference is: the global exclusive prefix sum, less the global exclusive prefix sum at the lane's segment start. Summing inside each segment instead would be a different number in floating point, and the kernel does this one.

ref.compact_ref

fn compact_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The positive values among the first n lanes of the first buffer, packed to the front of the second: leetgpu 72 (Stream Compaction). ins is [a, old out]. Everything after the last kept value keeps what it held, which is what leetgpu's reference does (it writes out[:k] and no more).

ref.linrec_ref

fn linrec_ref(b: Int, l: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

h[t] = a[t] * h[t-1] + x[t] along each of b rows of l, with h[0] = x[0]: leetgpu 82 (Linear Recurrence). ins is [a, x, old h].

The recurrence spelled out, which is the definition; the kernel folds affine maps instead, and the two agree only up to the tier. The kernel also fuses its multiply and add into one rounding and this does not, which is one more reason the tier is tolerance and not the bit.

ref.gae_ref

fn gae_ref(b: Int, s: Int, gamma: Float, lam: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The generalised advantage estimate along each of b rows of s: leetgpu 110 (Parallel Reverse Scan). ins is [rewards, values, old advantages].

delta[t] = rewards[t] + gamma * values[t+1] - values[t], with values[t+1] past the row's end taken as zero, and A[t] = delta[t] + gamma * lam * A[t+1] from the end. A loop backwards, where the kernel is one reverse scan.

ref.ssm_scan_ref

fn ssm_scan_ref(batch: Int, steps: Int, channels: Int, states: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The Mamba-style selective scan: leetgpu 94 (SSM Selective Scan). ins is [u, delta, A, B, C, skip, old y], with u and delta and y shaped (batch, steps, channels), A (channels, states), B and C (batch, steps, states) and skip (channels).

The recurrence written out, one channel at a time, where the kernel folds affine maps over a whole (step, state) plane at once. ref_exp is the host's own series, so the discretisation is a second opinion rather than the same call twice, and the tier is tolerance for that reason before any question of fold order arises.

ref.histogram_ref

fn histogram_ref(n: Int, bins: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

How many of the first n values of the first buffer fall in each of the first bins bins, into the second: leetgpu 13 (Histogramming). ins is [input, old histogram], and the lanes past bins keep what they held.

Values outside [0, bins) are not counted, which is leetgpu's own rule: its reference builds a validity mask before it counts. The problem's reference also ZEROES the histogram first, and so does this one, so a caller that hands it a buffer of anything but zeroes still gets the counts. The KERNEL cannot do that -- it only adds -- which is why every corpus for it starts the bins at zero and the two agree.

ref.cas_swap_ref

fn cas_swap_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[(Int, List[Float])]

Slot i of the first buffer takes lane i of the third where it held lane i of the second, and keeps what it had otherwise; the fourth buffer takes what the slot held BEFORE. A lane whose expected value is negative does not take part, and leaves both buffers alone. ins is [state, cmp, val, old] and the answer rewrites positions 0 and 3, which is the in-place shape knife 11 built.

n is how many slots the launch covered. Every slot is one lane's, so this says the whole of what the device does and there is no order in it.

ref.erf_sweep_ref

fn erf_sweep_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

erf(x) over the first n lanes: a coverage reference with no leetgpu problem behind it, the way int_ops and cas_swap have none. It is the one place the composition is compared to an accurate erf on its own, with nothing multiplied on top to hide where the error is.

ref.geglu_ref

fn geglu_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

x1 * (0.5 * x2 * (1 + erf(x2 / sqrt(2)))) where x1 is the first half of the input buffer and x2 the second, over the first n lanes of the output: leetgpu 65 (Gaussian Error Gated Linear Unit). n is half the input length, which is what the problem calls N // 2.

The gate is the EXACT gelu, 0.5 x (1 + erf(x / sqrt(2))), and not the tanh approximation: 65 says torch.erf. (74 GPT-2 Block, which the plan paired with this one, says F.gelu(approximate="tanh") instead, so it does not need an erf at all.)

ref.matmul_bt_ref

fn matmul_bt_ref(m: Int, k: Int, n: Int, scale: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

scale * (a * b^T) for an m by k matrix a and an n by k matrix b, both row-major, into an m by n output: the shape a linear layer has when its weights are stored out by in, which is how both leetgpu 85 (LoRA Linear) and leetgpu 6 (Softmax Attention) state theirs. ins is [a, b, old out].

Folded LEFT over k, and so a TOLERANCE-tier oracle for the same reason matmul_ref is: mmaf promises a product and nothing about the order of the sum.

ref.lora_out_ref

fn lora_out_ref(m: Int, r: Int, n: Int, scale: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

base + scale * (h * b^T) for the m by r hidden product h and the n by r weight b: the last of leetgpu 85's three launches, the one that adds the low-rank delta to the base linear it did not compute. ins is [base, h, b, old out].

ref.row_softmax_ref

fn row_softmax_ref(rows: Int, cols: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The softmax of each of rows rows of cols values: leetgpu 6's middle launch. The maximum is subtracted per row, as the kernel does. ins is [scores, old out].

ref.swiglu_act_ref

fn swiglu_act_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

silu(gate) * up over the first n lanes: the third of leetgpu 84's four launches, the only one of them that is not a matrix product. ins is [gate, up, old out].

ref.apsp_step_ref

fn apsp_step_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

One Floyd-Warshall round over an n by n distance matrix: d = min(d, d[:, k] + d[k, :]), with k read from the SECOND buffer's element 0. leetgpu 73 (All-Pairs Shortest Paths) is n of these, and the round index is a buffer rather than an argument because a launch here carries a grid and buffers and nothing else.

Writes back to argument 0: the round rewrites the matrix it read. ins is [dist, k].

Every read is of the matrix as it was BEFORE the round, which is what the problem's reference does as well (it builds a new tensor each round). On the device that is one block over one tile, so the loads all precede the store.

ref.flash_attn_ref

fn flash_attn_ref(m: Int, n: Int, d: Int, bk: Int, scale: Float, dtypes: List[String], ins: List[List[Float]], p_round: String = "") -> List[Float]

FlashAttention's ONLINE softmax, softmax(scale * q k^T) v for m queries against n keys of d features, one query row at a time and the keys bk at a time: the running maximum mx, the running sum l of the exponentials and the running weighted sum acc of the values, the first two rescaled by exp(mx_old - mx_new) whenever a block raises the maximum, and acc / l at the end. ins is [q, k, v, old o].

A second opinion on the fused kernel, and deliberately not the three-launch chain (matmul_bt_ref, row_softmax_ref, matmul_ref): that chain is what this is held to on the host, by the test at the end of this file, so the two references are two algorithms that must agree rather than one algorithm written twice. The first block's rescale is exp(-inf), which ref_exp answers 0.0, so nothing special-cases it.

p_round names the format the probabilities are narrowed to before the second product, "" for none: "bf16" is scripts/tile-golden's flash_attn_bf16, which writes p.to(BF16) there. Only that product sees the narrowed p; the running sum l adds the unrounded ones, as the kernel does, so the two halves of acc / l are not rounded alike on either side.

ref.causal_scores_ref

fn causal_scores_ref(m: Int, k: Int, n: Int, scale: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

leetgpu 53 (Causal Self-Attention): scale * (q k^T) where the key is not in the query's future, -inf where it is. ins is [q, k, old s].

ref.alibi_scores_ref

fn alibi_scores_ref(m: Int, k: Int, n: Int, scale: Float, alpha: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

leetgpu 55 (Attention with Linear Biases): ALiBi adds alpha * (i - j) to every score and masks nothing, so every lane here is finite. ins is [q, k, old s].

ref.window_scores_ref

fn window_scores_ref(m: Int, k: Int, n: Int, scale: Float, w: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

leetgpu 59 (Sliding Window Self-Attention): a band of half-width w about the diagonal, in both directions, and -inf outside it. The one rule of this family that is not causal. ins is [q, k, old s].

ref.sink_scores_ref

fn sink_scores_ref(m: Int, k: Int, n: Int, scale: Float, sinks: Int, w: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

leetgpu 112 (Attention with Sinks): causal, and within that either one of the first sinks keys or inside the trailing window of w. ins is [q, k, old s].

ref.decay_scores_ref

fn decay_scores_ref(m: Int, k: Int, n: Int, scale: Float, ln_gamma: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

leetgpu 92 (Decaying Causal Attention): the score times gamma^(i - j) where the key is not in the future, and zero where it is. There is no softmax in this problem at all, so these weights are what the context product reads. ins is [q, k, old s].

gamma^d is exp(d * ln gamma) here because that is what the kernel computes: ln gamma is a host constant of the recording, and a pow on each side would be two approximations of one power rather than one formula computed twice.

ref.cce_row_ref

fn cce_row_ref(rows: Int, cols: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

leetgpu 25's first launch: logsumexp(row) - row[label] for each of rows rows of cols logits. The maximum is subtracted per row, as the kernel does. ins is [logits, labels, old out], and labels is an i32 buffer, so its elements are whole numbers carried as Floats.

ref.mean_ref

fn mean_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The mean of the first n lanes into element 0: leetgpu 25's second launch, and the reason its answer is a scalar rather than a row. ins is [x, old out].

ref.matvec_ref

fn matvec_ref(m: Int, n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

y = A x for a DENSE m by n matrix and an n-vector: leetgpu 18 (Sparse Matrix-Vector Multiplication), whose own reference is torch.matmul over a buffer with zeroes in it. ins is [a, x, old y].

ref.subarray_sum2d_ref

fn subarray_sum2d_ref(cols: Int, r0: Int, r1: Int, c0: Int, c1: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The wrapping i32 sum of the rectangle [r0, r1] x [c0, c1] of a matrix cols wide, at element 0 of the second buffer: leetgpu 48 (2D Subarray Sum). Both bounds are INCLUSIVE, as the problem states them.

ref.subarray_sum3d_ref

fn subarray_sum3d_ref(rows: Int, cols: Int, d0: Int, d1: Int, r0: Int, r1: Int, c0: Int, c1: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The same over the box [d0, d1] x [r0, r1] x [c0, c1] of an array whose planes are rows by cols: leetgpu 49 (3D Subarray Sum).

ref.swiglu_half_ref

fn swiglu_half_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

silu(x1) * x2 with x1 and x2 the two halves of ONE buffer of n lanes each: leetgpu 54 (Swish-Gated Linear Unit). ins is [x, old out]. geglu_ref's shape with the swish gate in place of the error function one, and NOT swiglu_act_ref, which is handed two buffers a projection wrote.

ref.trig_sweep_ref

fn trig_sweep_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

sin cos tan sinh cosh atan2 remf over n lanes each, laid down in that order in ONE output buffer: segment j holds function j at offset j * n. The kernel writes seven masked stores at seven base offsets, so every lane of every function is compared and nothing is summed; a sum would let two wrong functions cancel, which is what the older mathops accepts and this one does not.

ins is [x, y, z, old out]: x is the argument of the five unary ones and the numerator of the atan2, y is that atan2's denominator (and carries the zeros the axes need), and z is the remainder's divisor (and carries no zero, because a remainder by zero is a NaN and a NaN is never within a tolerance of anything).

ref.rope61_ref

fn rope61_ref(m: Int, d: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

Q * cos + rotate_half(Q) * sin over an m by d buffer: leetgpu 61 (Rotary Positional Embedding). rotate_half swaps the two halves of a row and negates what lands first, so the answer at column j below d / 2 is q[j] * cos[j] - q[j + d/2] * sin[j] and at j above it is q[j - d/2] * sin[j] + q[j] * cos[j].

THIS READS cos AND sin AT THE FULL COLUMN j, and the kernel reads them at j mod (d / 2). Both are right because the problem guarantees the two tables are HALF-SPLIT (column j and column j + d/2 hold the same value), and the difference is the point: the kernel takes the shortcut the guarantee allows and the reference does not, so a corpus whose tables were not half-split would separate them. The corpus in scripts/tile-gpu-diff builds them half-split, which is what the problem hands a solver.

ins is [q, cos, sin, old out].

ref.dequant_ref

fn dequant_ref(m: Int, n: Int, ts: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

Y = X * S where one element of S scales a ts by ts block of an m by n matrix: leetgpu 64 (Weight Dequantization). S is ceil(m / ts) by ceil(n / ts), which is the problem's repeat_interleave then truncate read from the other side. ins is [x, s, old y].

ref.conv3d_ref

fn conv3d_ref(d: Int, h: Int, w: Int, kd: Int, kh: Int, kw: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The valid-mode convolution of a d by h by w volume with a kd by kh by kw kernel: leetgpu 11 (3D Convolution). The output is (d - kd + 1) by (h - kh + 1) by (w - kw + 1) and the taps are folded in the order the kernel's three host loops unroll them. ins is [x, w, old out].

ref.mha_scores_ref

fn mha_scores_ref(n: Int, dmodel: Int, heads: Int, scale: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

Q_head K_head^T * scale for every head of an n by dmodel pair, laid out one head's n by n block after another: leetgpu 12's first launch. Head hh owns the columns [hh * dk, (hh + 1) * dk) of both matrices, dk = dmodel / heads. ins is [q, k, old s].

ref.mha_context_ref

fn mha_context_ref(n: Int, dmodel: Int, heads: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

P_head V_head for every head, the answer written into the head's own columns of an n by dmodel output: leetgpu 12's third launch. ins is [p, v, old o].

ref.agent_step_ref

fn agent_step_ref(n: Int, r2: Float, alpha: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

One step of the flocking rule of leetgpu 14 (Multi-Agent Simulation): every agent moves by its velocity after being pulled alpha of the way towards the mean velocity of the agents within sqrt(r2) of it, and an agent with no neighbour keeps its own. ins is [agents, old out], and both are n agents of four floats.

ref.nearest_idx_ref

fn nearest_idx_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The nearest other point of each of n three-dimensional points, as an i32 index: leetgpu 38 (Nearest Neighbor). ins is [points, old out].

ref.xattn_scores_ref

fn xattn_scores_ref(m: Int, n: Int, dmodel: Int, heads: Int, scale: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

Q_head K_head^T * scale where the query has m rows and the key n, laid out one head's m by n block after another: leetgpu 26's first launch. Head hh owns the columns [hh * dk, (hh + 1) * dk) of a row of dmodel, dk = dmodel / heads, which is leetgpu 12's layout at two sequence lengths. ins is [q, k, old s].

ref.xattn_context_ref

fn xattn_context_ref(m: Int, n: Int, dmodel: Int, heads: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

P_head V_head for every head of leetgpu 26: m query rows over n key rows, the answer written into the head's own columns of an m by dmodel output. ins is [p, v, old o].

ref.gqa_scores_ref

fn gqa_scores_ref(hq: Int, hkv: Int, s: Int, d: Int, scale: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

Q_h K_kv^T * scale for every query head of leetgpu 80 (Grouped Query Attention), where head h reads key head h / (hq / hkv). Every head is a CONTIGUOUS s by d matrix here, so the group is a division and not a column offset. ins is [q, k, old s].

ref.gqa_context_ref

fn gqa_context_ref(hq: Int, hkv: Int, s: Int, d: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

P_h V_kv for every query head of leetgpu 80, with the same grouping. ins is [p, v, old o].

ref.grpo_adv_ref

fn grpo_adv_ref(b: Int, g: Int, eps: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The group-normalised advantage of every reward of leetgpu 109 (GRPO Surrogate Loss): b groups of g rewards, each centred on its group's mean and divided by its group's BIASED standard deviation plus eps. ins is [rewards, old adv].

ref.grpo_row_ref

fn grpo_row_ref(rows: Int, s: Int, clip: Float, beta: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

Minus the mean over s tokens of the clipped surrogate less beta times the k3 KL penalty, one row a lane: leetgpu 109's second launch. The negation is here and not after the mean because negation is exact, so the mean of the negated rows is the negated mean and the third launch can be a plain average. ins is [log_pi, log_pi_old, log_ref, adv, old rows].

ref.kmeans_assign_ref

fn kmeans_assign_ref(n: Int, k: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The nearest centroid of each of n two-dimensional samples, as an i32 label: leetgpu 20's first launch, run once a round. ins is [data_x, data_y, centroid_x, centroid_y, old labels].

ref.kmeans_centroid_ref

fn kmeans_centroid_ref(n: Int, k: Int, dtypes: List[String], ins: List[List[Float]]) -> List[(Int, List[Float])]

Both centroid buffers of leetgpu 20 after one round, IN PLACE: the two coordinates are two output buffers, and a cluster nothing was assigned to keeps the centroid it had. ins is [data_x, data_y, labels, centroid_x, centroid_y], and the answer names the last two argument positions.

ref.kv_scores_ref

fn kv_scores_ref(heads: Int, seq: Int, d: Int, inv_sqrt_d: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

leetgpu 96's first launch: s[h, j] = (Q[h] . dequant(K)[h, j]) / sqrt(head_dim), heads rows of seq. ins is [Q, K_int8, k_scale, old s].

ref.kv_context_ref

fn kv_context_ref(heads: Int, seq: Int, d: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

leetgpu 96's third launch: o[h, dd] = sum_j p[h, j] * dequant(V)[h, j, dd], heads rows of head_dim. ins is [p, V_int8, v_scale, old o].

ref.attn_bwd_mmt_ref

fn attn_bwd_mmt_ref(m: Int, n: Int, d: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

c = a^T b for an m by n matrix a and an m by d one, into an n by d output: leetgpu 111's dV = P^T dO and its dK = dS^T Q, which are the same product on different operands. ins is [a, b, old c].

Folded LEFT over m, so a TOLERANCE-tier oracle for mmaf's reason.

ref.attn_bwd_ds_ref

fn attn_bwd_ds_ref(rows: Int, cols: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The softmax Jacobian applied to a row: dS = dP * P - P * rowsum(dP * P) over rows rows of cols, leetgpu 111's fifth launch. ins is [dP, P, old dS].

The 1 / sqrt(d) the problem divides by is NOT here: the expression is linear in dP, and the launch that produced dP is attn_scores, which already divided. Moving a scale to the launch that has one is what makes this problem cost two kernels instead of four.

ref.elu_plus_one

fn elu_plus_one(x: Float) -> Float

phi(x) = ELU(x) + 1: x + 1 above zero and exp(x) at or below it. leetgpu 56's feature map, and the reason its two kernels have no expm1 in them: ELU(x) = exp(x) - 1 there and the + 1 cancels exactly.

ref.lin_attn_s_ref

fn lin_attn_s_ref(m: Int, d: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

leetgpu 56's first launch: S[a, b] = sum_j phi(K[j, a]) * V[j, b], a d by d answer over m rows. ins is [K, V, old S].

Its SECOND launch is this same function with a buffer of ones where V is, which answers z[a] = sum_j phi(K[j, a]) in every column.

ref.lin_attn_out_ref

fn lin_attn_out_ref(m: Int, d: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

leetgpu 56's third launch: out[i, b] = (phi(Q) S)[i, b] / (phi(Q) z)[i], where z is the first COLUMN of the d by d matrix the second launch wrote. ins is [Q, S, Z, old out].

ref.ols_gram_ref

fn ols_gram_ref(n: Int, f: Int, w: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

leetgpu 33's first launch: the augmented normal equations of X^T X beta = X^T y as an f by w matrix, columns 0..f-1 holding X^T X, column f holding X^T y and the rest zero. ins is [X, y, old A].

Folded LEFT over the samples, which is the order the kernel's d_range adds them in, so this oracle is EXACT and not a tolerance claim: there is no mmaf here and no reduction whose tree is unspecified.

ref.ols_elim_ref

fn ols_elim_ref(f: Int, w: Int, dtypes: List[String], ins: List[List[Float]]) -> List[(Int, List[Float])]

One Gauss-Jordan step of leetgpu 33 IN PLACE: row k becomes itself divided by its pivot and every other row loses its k-th multiple of it, with k read from the SECOND buffer's element 0 the way apsp_step_ref reads its round. ins is [A, k], and the answer names argument 0.

No pivot search: A's leading f columns are a Gram matrix, which is symmetric positive definite, so every leading minor is positive and every pivot this visits is non-zero.

ref.ols_beta_ref

fn ols_beta_ref(f: Int, w: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

leetgpu 33's last launch: beta is column f of the reduced augmented matrix. ins is [A, old beta].

ref.linear_bias_ref

fn linear_bias_ref(m: Int, k: Int, n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

a * W + b, an m by k matrix against a k by n weight stored IN by OUT with one bias per output column: the shape every linear layer of leetgpu 74 has, at four different (k, n). ins is [a, w, bias, old out].

Folded LEFT over k, so a TOLERANCE-tier oracle for matmul_ref's reason: mmaf promises a product and nothing about the order of its sum.

ref.gpt_scores_ref

fn gpt_scores_ref(s: Int, d: Int, heads: Int, scale: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

Q_h K_h^T * scale for every head of leetgpu 74, both operands read out of the PACKED projection buffer: s rows of 3 * d values, Q in the first d columns and K in the next d, head h owning columns h * d / heads of each. ins is [qkv, old s].

ref.gpt_context_ref

fn gpt_context_ref(s: Int, d: Int, heads: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

P_h V_h for every head of leetgpu 74, V read from the LAST third of the same packed buffer and the answer written into head h's columns of an s by d output. ins is [p, qkv, old o].

ref.gelu_tanh_ref

fn gelu_tanh_ref(n: Int, c: Float, kk: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

0.5 * x * (1 + tanh(c * (x + kk * x^3))) over the first n lanes: the TANH approximation of the gaussian error linear unit, which is what leetgpu 74's feed-forward asks for (F.gelu(x, approximate="tanh")) and not the erf form of leetgpu 65. ins is [x, old out].

ref.llama_rms_ref

fn llama_rms_ref(rows: Int, d: Int, eps: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

x * rsqrt(mean(x^2) + eps) * w per ROW of a rows by d matrix: the root-mean-square normalisation of leetgpu 93, which has a learned weight and no residual (leetgpu 83's has both, leetgpu 50's has neither). ins is [x, w, old], with w d long.

ref.head_major_bt_ref

fn head_major_bt_ref(s: Int, din: Int, hd: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The head COUNT is read off the output buffer rather than fixed here, the way the kernel reads it off the grid: leetgpu 93 launches the same projection at four query heads and at two key heads, and the buffer's total width enters neither.

One projection of leetgpu 93 written HEAD-MAJOR: out[h][i][c] = sum_j x[i][j] * W[h * hd + c][j] for a weight stored OUT by IN. The same numbers matmul_bt_ref computes, laid out head by head instead of token by token, which is the layout knife 19's grouped-query kernels read. ins is [x, w, old out].

ref.rope_ref

fn rope_ref(s: Int, hd: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The head count is read off the output buffer, for head_major_bt_ref's reason: the same rotation runs over four heads and over two.

The rotary position embedding of a head-major heads by s by hd buffer: the two halves of each head's row rotated by the angle of that POSITION, [x1 * cos - x2 * sin, x1 * sin + x2 * cos]. cos and sin are s by hd / 2 tables every head shares, and leetgpu 93 hands them in as buffers, so nothing here computes a trigonometric function. ins is [x, cos, sin, old out].

ref.gqa_causal_scores_ref

fn gqa_causal_scores_ref(hq: Int, hkv: Int, s: Int, d: Int, scale: Float, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

gqa_scores_ref with a CAUSAL mask: the score at a key in the query's future is -inf, so leetgpu 93's softmax gives it weight exactly zero. ins is [q, k, old s].

ref.llama_out_ref

fn llama_out_ref(s: Int, d: Int, hd: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

Leetgpu 93's output projection: the head-major attention output read as one s by d matrix and multiplied by a weight stored OUT by IN, out[i][o] = sum_h sum_t a[h][i][t] * W[o][h * hd + t]. The merge of the heads is this indexing and no launch of its own. ins is [a, w, old out].

ref.shape_ops_ref

fn shape_ops_ref(blocks: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The three shape operations over one block of 128 f64, read two ways: ins is [x, old out]. Block b reads x[b * 128 ..] and fills 192 lanes: the first 64 are the 16 by 8 tile with its two row halves exchanged and only its first four columns kept (extract twice, then cat the other way round), and the next 128 are the 4 by 4 by 8 tile with its first two axes swapped (permute [1, 0, 2]).

ref.grid_stride_ref

fn grid_stride_ref(tiles: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

2 * x over tiles tiles of 128 lanes: ins is [x, old out].

The reference does not model the grid at all, and that IS the claim. The kernel covers the tiles with a grid-stride loop over a grid smaller than the work, so a loop that visited every tile exactly once answers this and one that missed a tile (leaving the sentinel) or repeated one instead of another does not.

ref.token_join_ref

fn token_join_ref(blocks: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The low half of each 128-lane chunk doubled and the high half tripled, written into a scratch buffer by two INDEPENDENT chains of stores and read back through the token that joins them: ins is [x, scratch, old out].

The scratch buffer's own contents never appear here, which is the judgement: it is handed a sentinel no lane of the answer holds, so a load that was not ordered after both stores would show.

ref.ptr_roundtrip_ref

fn ptr_roundtrip_ref(blocks: Int, delta: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

x[i + delta] over blocks tiles of 128 lanes, the address having gone through i64 and back: ins is [x, old out]. The input buffer carries delta elements past the last block's chunk, which is why the answer is an ordinary shifted copy and not a masked one.

ref.ptr_recast_ref

fn ptr_recast_ref(blocks: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The binary32 value whose bit pattern is the i32 lane: ins is [patterns, old out]. The kernel reads the same four bytes through a pointer of another element format; this reads them by taking the pattern apart, which is a second opinion about the FORMAT and not only about the address.

ref.insert_tile_ref

fn insert_tile_ref(blocks: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The insert kernel over one block of 128 f64 read as a 16 by 8 tile: ins is [x, old out]. Block b fills 256 lanes from x[b * 128 ..]. The first 128 are the tile itself, which is what putting the quarter at slice [1, 0] back where it came from answers. The next 128 are the tile with its quarter at slice [0, 1] (rows 0 to 7, columns 4 to 7) replaced by the one at [1, 0] (rows 8 to 15, columns 0 to 3); every other lane is the destination's own.

ref.ref_fpowi

fn ref_fpowi(x: Float, n: Int) -> Float

x raised to the integer power n the way the device's fpowi does it over f64, which is NOT the correctly rounded power. Measured on this tree's RTX 3080 (tileiras 13.4.92, sm_86, docs/tile-backend-design.md 6.19): the device answers right-to-left binary exponentiation of x to |n| -- square the base once a bit, multiply it into the result where the bit is set, every step one rounded binary64 product -- and for a negative n the RECIPROCAL of that, one rounded division at the end. Bit for bit on every lane of the probe corpus, where the power of the reciprocal ((1/x)^|n|) agrees on two lanes in three and repeated multiplication on fewer than half.

What falls out of that sequence is the whole of the edge-case semantics, and every one of these is the device's answer too: x^0 is 1.0 for EVERY x, NaN, infinities and both zeroes included (the loop never runs); 0^n and (-0)^n for a negative n are the infinities the final division by a zero of that sign gives, so (-0)^-1 is -inf and (-0)^-2 is +inf; a power that overflows on the way is inf before the division, so its reciprocal is a zero, and one that underflows to a subnormal is divided as the subnormal it rounded to.

ref.powi_sweep_ref

fn powi_sweep_ref(blocks: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The fpowi kernel over 128-lane blocks: ins is [x, n, old out], x the f64 bases and n the i32 exponents. Block b fills 256 lanes: the first 128 are x^n, the next 128 are x raised to the LOW OCTET of n read as a signed i8 (the kernel truncis the exponent to i8 and the dialect reads every exponent width as signed), so an exponent of 200 is the power -56 there.

ref.f32_flush

fn f32_flush(x: Float) -> Float

The number a flush_to_zero operation sees or answers in place of x: a subnormal binary32 becomes the zero of its sign, everything else is itself. The smallest normal binary32 is 2^-126.

ref.attr_round_ref

fn attr_round_ref(blocks: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

Six directed roundings of one pair of binary32 operands: the sum, the product and the quotient, each towards negative and towards positive infinity, in blocks blocks of 128 lanes and six segments each. ins is [x, y, old out].

The sum and the product of two binary32 values are EXACT in binary64 (48 significand bits at worst, and both exponents well inside the range), so the two rounding modes are applied to the exact result and the answer is the format's own. The quotient is not exact, and the corpus is what makes it safe: the divisors are small odd integers scaled by powers of two, so the exact quotient is never within 2^-53 of a binary32 grid point and rounding the binary64 quotient in the named direction lands where rounding the exact one would. attr_diff prints the corpus's divisors so that a change to it is visible.

ref.attr_nan_ref

fn attr_nan_ref(blocks: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The two comparison orderings and the two NaN policies over one pair of f64 tiles, in blocks blocks of 128 lanes and sixteen segments each: the six predicates ordered, the same six unordered, then maxf and minf with and without propagate_nan. ins is [x, y, old out].

What the dialect says, and what this therefore says: an ORDERED comparison answers false whenever either operand is a NaN, an UNORDERED one answers true; maxf without propagate_nan is IEEE 754-2019's maximumNumber (the non-NaN operand, and a canonical NaN only when both are), with it maximum (a canonical NaN if either is).

ref.attr_ftz_ref

fn attr_ftz_ref(blocks: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The sum and the product of one pair of binary32 operands with and without flush_to_zero, in blocks blocks of 128 lanes and four segments each. ins is [x, y, old out].

The attribute flushes SUBNORMAL INPUTS as well as subnormal results, which is what the dialect's own wording says ("flushes subnormal inputs and results to sign-preserving zero") and what this reference therefore does: flush both operands, do the operation, flush the answer.

ref.attr_approx_ref

fn attr_approx_ref(blocks: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The square root of one binary32 tile, twice: rounding<nearest_even> and rounding<approx>, in blocks blocks of 128 lanes and two segments. ins is [x, old out].

Both segments answer the same thing here, which is the whole of what this reference can say: the approximate root is the ARCHITECTURE's approximation and no host expression is its definition. This is a tolerance-tier kernel, and what says the attribute reached the device is the DISTANCE between the two segments, which attr_diff measures and run.sh pins above zero.

ref.attr_overflow_ref

fn attr_overflow_ref(blocks: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The sum, the difference and the product of two i32 tiles under the three overflow assumptions, in blocks blocks of 128 lanes and three segments each. ins is [x, y, old out].

wrap_i32 is applied as it is everywhere else here, and on this corpus it never has anything to do: an operation that wrapped under no_signed_wrap would be undefined and the reference would be answering a question the dialect refuses to answer.

ref.attr_memsem_ref

fn attr_memsem_ref(blocks: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

Three atomic integer additions under three memory orderings and scopes, each into its own 128 slots of a buffer the kernel also reads back: ins is [x, old out] and the answer is the new contents of the second. blocks blocks own 384 slots each, so no two lanes anywhere name one slot.

That is the point and the limit: an ordering constrains CONCURRENT accesses, this corpus has none, and so what the device can say here is that the atomic ran and added. attrs.txt records the five values at layer 1 with that reason.

ref.attr_addf_ref

fn attr_addf_ref(blocks: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The float atomic read-modify-write: every one of blocks blocks adds its own chunk of x into the SAME 128 slots. ins is [x, old out].

The lanes collide across blocks, so the order they arrive in is not defined; the corpus is small integers, whose sum is exact in binary64 under any order, so the tier is exact by algebra rather than by luck.

ref.attr_ucmp_ref

fn attr_ucmp_ref(blocks: Int, lo: Int, hi: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The iteration counts of two loops over one range that straddles 2^31, one comparing its bound as a signed i32 and one as an unsigned one: ins is [old out] and each of blocks blocks fills 256 slots, the first 128 with the signed count and the next 128 with the unsigned one.

lo and hi are the bounds the kernel was recorded with. Read as signed, hi is negative and below lo, so the loop does not run at all; read as unsigned it is hi - lo above it. This is the one unit attribute of knife T4 whose effect on the device is an integer rather than a rounding.

ref.attr_sat_ref

fn attr_sat_ref(blocks: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

ftoi from f64 to i32 with and without the saturating modifier (knife T17), in blocks blocks of two 128-lane segments. ins is [x, old out].

The first segment is the modifier's definition in Ops.td: truncate toward zero, then a lane above i32's largest value is that value, one below its smallest is that one, and a NaN is 0. The second holds only the lanes whose truncation IS an i32 (-2^31 - 1 < x < 2^31) and keeps the output buffer's own value everywhere else, because the kernel stores it only there: without the modifier the other lanes are undefined behaviour, and what one device answers for them is not a reference.

ref.attr_ftof_ref

fn attr_ftof_ref(blocks: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

ftof under the rounding modes bytecode 13.4 newly allows (knife T17), each beside the nearest_even it differs from, in blocks blocks of eleven 128-lane segments. ins is [x, old out]. Segment by segment:

0-3 x to f32: nearest_even, zero, negative_inf, positive_inf 4-6 segment 0 to tf32: nearest_even, zero, nearest_away 7-8 segment 0 to f16: nearest_even, zero 9-10 segment 0 to bf16: nearest_even, zero

Every one is std/narrow's rounding of the exact value: x is a binary64, so the first four round it once, and segment 0 is a binary32, which every later target rounds once more from. The widening back to f64 that the kernel does after each is exact, whatever mode it is written in, and so does not appear here.

ref.attr_xchg_ref

fn attr_xchg_ref(slots: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The atomic exchange (knife T17): every lane i of the first slots of ins[0] swaps itself into slot slots - 1 - i of the output, and what that slot held is stored at slots + i. ins is [x, old out]. No two lanes name one slot, so the order they arrive in cannot matter, and the answer is two reversals: the input into the first half, the output buffer's own first half into the second.

ref.dtype_i16_ref

fn dtype_i16_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The five segments of dtype_i16: the sum, the difference, the product, (a << 3) >> 2 and the itof / ftoi round trip, each wrapped into [-32768, 32767] as the device's i16 arithmetic wraps. ins is [a, b, out].

The round trip is the IDENTITY and is here for that reason: every i16 is a binary64 exactly, so widening and truncating back cannot lose anything, and a segment that is not the input is a conversion at the wrong width.

ref.dtype_i64_ref

fn dtype_i64_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The five segments of dtype_i64: the sum, the difference, the product, the sum with the constant 2^32 and the itof / ftoi round trip. ins is [a, b, out].

NO WRAPPING HERE, which is the difference from the i16 reference and a statement about the corpus rather than about the format: the device wraps at 2^64 and this cannot follow it there, so the corpus stays inside +/-2^52 (dtype_diff.dawn counts the products that leave the i32 range, which is what makes the width itself visible).

ref.dtype_i64_ref_int

fn dtype_i64_ref_int(n: Int, a: List[Int], b: List[Int]) -> List[Int]

dtype_i64_ref over Ints, for a corpus that leaves +/-2^53: the same five segments (n lanes each, 5 * n answers, no output buffer to keep a tail of), computed in the Ints the device computes in. Int addition, subtraction and multiplication wrap at 2^64 here as the device's addi, subi and muli do, so nothing in the three arithmetic segments is approximate, and the constant segment is exact too. The last segment is the kernel's own itof / ftoi round trip and it is NOT the identity past 2^53: to_float rounds to the nearest binary64 (ties to even) and to_int truncates it back, which is the device's itof and ftoi.

Why a twin and not a change to dtype_i64_ref: the fake device hands its references List[Float] (WideRefFn), and an i64 past 2^53 cannot be one. This function is called by a driver that holds the corpus as Ints and compares with a real device's typed download.

ref.dtype_i4_ref

fn dtype_i4_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The five segments of dtype_i4: the nibble-wise sum, the sign-extended nibble shifted right by one, the zero-extended one shifted right by one, the nibble-wise product, and a's nibble on even lanes with b's on odd ones. ins is [a, b, out], and every buffer is i32: there is no buffer of i4 at all, so a word carries eight lanes and this reference reads them the way unpack does (nibble_at).

SEGMENTS 1 AND 2 ARE THE SAME SHIFT under the two widenings, and they differ on exactly the lanes whose top bit is set. Nothing else here can see the difference: + - * agree modulo 16 whichever widening produced the operands, which is why the kernel carries a shift at all.

ref.pack_roundtrip_ref

fn pack_roundtrip_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The two segments of pack_roundtrip: the bytes that went in, twice. ins is [a, out] and both buffers are i32.

This reference names no nibble and no half word, which is what makes it the CONTROL for pack-halves-swapped: a round trip through a format is blind to the order the lanes were laid down in, because it lays them back the same way. The kernel is the identity and so is this.

ref.dtype_convert_ref

fn dtype_convert_ref(n: Int, dtype: String, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The two segments of dtype_tf32, dtype_e4m3, dtype_e5m2 and dtype_e8m0: the buffer of the format widened to f64, and an f64 taken into the format and back. ins is [x, y, out] and dtype names the format.

Segment 0 is not a rounding: the buffer already holds values of the format (copy_from_host rounded them, encode_floats laid them down) and widening is exact, so the reference is the buffer. That is the whole of what a device can be wrong about there, and it is exactly the storage layout.

Segment 1 for f8E8M0FNU goes through binary32, because the KERNEL does: ftof from f64 straight into that format kills tileiras 13.3.36, so scripts/tile-golden's kernel detours and this rounds twice to match. For the other three it is one rounding.

ref.dtype_e2m1_ref

fn dtype_e2m1_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The two segments of dtype_e2m1: the packed nibbles of ins[0] read as f4E2M1FN values and widened to f64, and an f64 taken into the format and back. ins is [x, y, out], x is an i32 buffer and y and out are f64 ones.

THIS IS NOT dtype_convert_ref WITH ANOTHER NAME, and segment 0 is the reason. Every other narrow float format has a buffer of its own, so copy_from_host rounds the corpus into the format and segment 0 is the buffer as it stands. f4E2M1FN has none (element_bytes refuses it), so its kernel brings the bits in as i32 words and unpacks them, and the reference has to read the same words the same way: lane i is nibble i % 8 of word i / 8, low nibble first, and narrow.f4e2m1_of_bits says which of the sixteen values that nibble is. What segment 0 asks is therefore the ENCODING and not a rounding.

Segment 1 is one rounding, as it is for f8E4M3FN and f8E5M2: round_to dispatches to narrow.round_f4e2m1, and the widening back to f64 is exact.

ref.mmaf_scaled_ref

fn mmaf_scaled_ref(m: Int, k: Int, n: Int, v: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The block-scaled matrix product mmaf_scaled_e4m3 computes, as one m by n tile widened to f64. ins is [a, b, sa, sb, out]: a is m by k and b is k by n in the operand format, sa is m by k / v and sb is k / v by n in the scale format, and every buffer is laid out row-major.

THE FORMULA IS Ops.td's, transcribed rather than guessed. MmaFScaledOp writes it out:

result[i][j] = acc[i][j] + sum over k of (a[i][k] sa[i][k / v]) (b[k][j] * sb[k / v][j])

so v is the BLOCK: one scale element covers a run of v consecutive elements along k, and k / v is integer division. v is not an operand and not an attribute -- Ops.td has no such parameter -- it is the ratio of the two shapes, which is why it is an argument here and not something this could read off the buffers.

The accumulator is zero and is therefore not a parameter: the kernel builds it with f_const and no host buffer carries it.

THE FOLD ORDER IS NOT PINNED, and the corpus is what makes that harmless. The device accumulates in f32 in an order the dialect does not specify, so a reference that folded left would be one reading among many; the corpus scripts/tile-gpu-diff/arch_diff.dawn ships keeps every product and every partial sum a whole number well inside 2^24, where f32 addition is exact and therefore associative. What is left for the device to be wrong about is the scaling itself.

ref.collatz_step_ref

fn collatz_step_ref(v: Int, steps: Int) -> (Int, Int)

The Collatz successor of v, saturating at 1: a lane that has reached 1 stays there, and the step is counted only while the lane is still moving. The device computes both branches and selects, and wraps at 32 bits; this does the same, so 3 * v + 1 past 2^31 wraps here too.

ref.loop_count_ref

fn loop_count_ref(n: Int, fuel: Int, segs: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The step count of every one of the first n lanes of ins[0], and the number of iterations the loop ran repeated across the next n: the reference for loop_count and, without the second segment, for loop_bound.

The round count is the LARGEST step count, and that is an argument rather than a definition: every lane that is still moving adds one on every iteration and the loop stops on the first iteration where none is, so the number of iterations that changed anything is the longest chain. The kernel computes it with a second reduction over the counts and this computes it with a fold; both are the maximum, and the device is where the two are held together.

ref.loop_until_ref

fn loop_until_ref(n: Int, eps: Float, fuel: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The square root of each of the first n lanes of ins[0] by Newton's iteration from 1.0, and the number of rounds it took repeated across the next n: the reference for loop_until.

EXACT TIER BY ALGEBRA. Every operation in the iteration is one IEEE 754 requires to be correctly rounded, the lanes are independent, and the order is the same on both sides, so the tiles agree bit for bit. What the round count adds is that the two sides also agree on WHEN to stop, and that is the part only a data-dependent loop can be wrong about: the residual falls by five orders of magnitude between the last iteration and the one before it for the corpus this is used with, so the two sides cannot disagree about the comparison by a rounding.

ref.loop_none_ref

fn loop_none_ref(n: Int, threshold: Float, step: Float, fuel: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

The first n lanes of ins[0], unchanged, when every one of them is under threshold, and step added to each otherwise: the reference for loop_none, whose loop stops before its body has changed anything.

It is written as the general loop rather than as "the input" so that it is a reference and not a restatement of the corpus: hand it a tile over the threshold and it iterates, exactly as the kernel does.

The step moves the tile AWAY from the exit, so a tile that starts over the threshold never reaches it and fuel is what stops this function. That asymmetry is the kernel's and it is deliberate: it is what makes the loop-break-condition-inverted mutant stop after one iteration rather than never (scripts/tile-gpu-diff/run.sh says so at the mutant).

ref.loop_return_ref

fn loop_return_ref(blocks: Int, at: Int, limit: Int, fuel: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

loop_return (knife T17): each of blocks blocks of 128 lanes of ins[0] runs the kernel's loop, and its 256 output slots say how. The first 128 hold how many iterations the block BEGAN (the kernel stores the count at the top of every iteration); the next 128 hold the tile the loop broke with, if it broke, and the output buffer's own value if the block returned instead, because the store after the loop is then never reached.

The order inside an iteration is the kernel's: the count is stored, then the block returns if its ENTERING tile holds a value at or above at, then it breaks if the count has reached limit, then every lane goes up by one. So a block that reaches at on its limit-th iteration returns rather than breaks. fuel bounds this function and is never what stops it for a corpus the kernel can finish.

ref.assert_pass_ref

fn assert_pass_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

Knife T6's references. All four are one line of arithmetic each, and that is the point: what these kernels are FOR is an operation that does not change the answer (assume), one that stops the launch (assert) or one that writes to the process's standard output (print_tko), so the buffer they leave behind has to be something a host can restate without effort. Anything harder would move the comparison off the operation under test.

assert_pass: the first n lanes plus one. The kernel's assertion holds on every lane of the corpus it is run with, so this is what a launch that does not fire answers. There is no reference for assert_fail: a launch that fires has no answer to compare, and its verdict is the error the host is given.

ref.assume_divby_ref

fn assume_divby_ref(n: Int, divisor: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

assume_divby: the first n lanes divided by divisor. The kernel assumes every lane is a multiple of it, and the corpus makes that true, so the quotient is exact on both sides and the assumption is a claim about code generation and not about the answer.

ref.assume_same_ref

fn assume_same_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

assume_same: the first n lanes times three.

ref.assume_bounded_ref

fn assume_bounded_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

assume_bounded: the first n lanes plus five.

ref.print_tile_ref

fn print_tile_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

print_tile: three segments of n lanes -- the input unchanged, then the sum and the maximum of the first n lanes repeated across the other two. The same two numbers the kernel PRINTS, which is what lets the bytes on standard output and the bytes in the buffer be two readings of one answer.

ref.print_tile_sum

fn print_tile_sum(x: List[Float], n: Int) -> Int

The sum of the first n lanes, modulo 2^32 as an i32 addi folds it. Public because scripts/tile-gpu-diff computes the line print_tile prints from it, and that line is a judgement of its own.

ref.print_tile_max

fn print_tile_max(x: List[Float], n: Int) -> Int

The largest of the first n lanes, as a signed i32 maxi folds it.

ref.global_table_a

fn global_table_a(n: Int) -> List[Float]

The two tables global_table's module declares as static globals, and the values the host says they hold. They are here rather than in the kernel source because BOTH sides need them: the kernel bakes them into the Global section's initializer and the reference below answers with them, and the whole judgement is that the device reads back what the host declared.

Neither table is a palindrome and neither is a splat, which is what the two writer mutants need: an initializer written backwards and one written as a one-element splat are both legal Tile IR, and only a corpus that can tell them from the truth says so.

ref.global_table_b

fn global_table_b(n: Int) -> List[Float]

The second of global_table's two tables, declared with an alignment: -(i * i) - 0.25 at lane i, negative everywhere and falling, so it disagrees with global_table_a on every lane.

ref.global_table_ref

fn global_table_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

global_table: two segments of n lanes, each the corresponding table plus the input, and the rest of the output buffer left alone. ins is [x, out].

EXACT TIER BY ALGEBRA: one addf per lane with rounding<nearest_even>, which is what a binary64 + is.

ref.global_ctl_ref

fn global_ctl_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

global_ctl: the same two segments computed from two BUFFERS instead of two globals. ins is [a, b, x, out]. The kernel it answers for holds neither global nor get_global, so it is the T7 family's kernel-level control and a second opinion on the tables at the same time.

ref.global_scratch_ref

fn global_scratch_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

global_scratch: the output buffer plus the input, lane by lane. ins is [x, out].

THIS ONE IS NOT THE KERNEL'S ALGORITHM, and that is the point. The kernel adds its input to a mutable GLOBAL and writes the sum to both; the reference adds it to the OUTPUT BUFFER, which is where the host can see the same running total. The two agree only while the global keeps its value from one launch to the next, so a sequence of launches of the same module is a judgement about the lifetime of a static allocation and not about arithmetic. The global's initializer is zeros and so is the output buffer's first n lanes, which is what starts the two totals together.

ref.global_syms_c

fn global_syms_c(n: Int) -> List[Float]

The third table global_syms's module declares, the one its constant global holds. global_table_a and global_table_b are the other two. Like them it is neither a palindrome nor a splat, and it disagrees with both of them on every lane, so the three segments of one output buffer tell three symbols apart.

ref.global_syms_tables

fn global_syms_tables(n: Int) -> List[List[Float]]

The three tables global_syms's module declares, in declaration order: @shown public, @hidden private, @frozen constant.

ref.global_syms_ref

fn global_syms_ref(n: Int, tables: List[List[Float]], dtypes: List[String], ins: List[List[Float]]) -> List[Float]

global_syms: three segments of n lanes, each one of the module's three globals plus the input, and the rest of the output buffer left alone. ins is [x, out].

tables is what the three globals hold AT THE TIME OF THE LAUNCH, which is a parameter rather than global_syms_tables(n) because the host can write a module's global between launches: whether a private or a constant global accepts such a write, and whether the next launch reads it, is a question about the device, and a reference that baked the initializers in could not ask it.

EXACT TIER BY ALGEBRA: one addf per lane with rounding<nearest_even>, which is what a binary64 + is.

ref.alloca_scratch_ref

fn alloca_scratch_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

alloca_scratch: three times the input, lane by lane. ins is [x, out].

THE ALGEBRA IS THE JUDGEMENT HERE, and not the arithmetic. The kernel writes x + x into a fresh automatic allocation, reads it back through the same pointers in the same launch and adds the input to what it read; this answers (x + x) + x directly. The two agree only while the allocation is memory that keeps what was put in it, so a scratch that read back zeros would answer x and a store that never landed would leave the sentinel.

EXACT TIER BY THE VALUES: every lane of the corpus is a small multiple of a power of two, so both sums are exact in binary64 and the rounding<nearest_even> the kernel carries has nothing to round.

ref.alloca_two_ref

fn alloca_two_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

alloca_two: 2 * x + 1, lane by lane, which is x - (-x - 1). ins is [x, out].

THIS ONE IS A DISTINCTNESS CLAIM. The kernel makes two allocations in one block, stores x into the first and -x - 1 into the second, and answers the difference. Two allocations at the same address would answer 0 for every lane, because the second store would have overwritten the first; the corpus keeps every lane away from -0.5, the one input where 2 * x + 1 is 0 in its own right.

ref.alloca_ctl_ref

fn alloca_ctl_ref(n: Int, dtypes: List[String], ins: List[List[Float]]) -> List[Float]

alloca_ctl: alloca_scratch's answer with no allocation in it. ins is [x, out]. The kernel it answers for holds no alloca at all, so it is this family's kernel-level control: no mutant of the allocation may move its bytes or its verdict.

ref.scalar_scale_ref

fn scalar_scale_ref(n: Int, ins: List[List[Float]], scalars: List[Float]) -> List[Float]

scalar_scale: ins is [x, out] and scalars is [s]. The first n lanes of out are x * s in binary64 (the kernel widens its f32 scalar first, which is exact); the rest of out is its own tail.

ref.scalar_len_ref

fn scalar_len_ref(ins: List[List[Float]], scalars: List[Float]) -> List[Float]

scalar_len: ins is [x, out] and scalars is [n]. Lane i of out is x[i] for i < n and what out held for every other lane: the mask bound is the launch's value, and a negative one masks every lane.

ref.scalar_loop_ref

fn scalar_loop_ref(tile: Int, ins: List[List[Float]], scalars: List[Float]) -> List[Float]

scalar_loop: ins is [x, out], scalars is [n] and tile is the block's width. The first tile lanes of out are the first n tiles of x summed lane by lane in order, starting from zero, which is the loop's own fold; n below one is the empty sum, zeros. The rest of out is its own tail.

ref.scalar_wide_ref

fn scalar_wide_ref(n: Int, ins: List[List[Float]], scalars: List[Float]) -> List[(Int, List[Float])]

scalar_wide: ins is [xf, xi, out_f, out_i] and scalars is [a, k]. out_f is xf * a and out_i is xi + k, each over the whole of its buffer's first n lanes. Answers the two buffers written, as the (position, contents) pairs a WideRefFn answers.