chipintegrity.org GitHub
chipintegrity.org / findings / metal-pytorch-2-2-2
FINDING, 21 OCTOBER 2026

Metal through PyTorch 2.2.2 sits outside the arithmetic model

On an AMD Radeon Pro 5500M, the backend's sin and cos measured 1.22e-4 against the model's 9.5e-7 allowance, and four more fp16 steps sat outside their bound while every run agreed with every other.

What was measured

On 21 October 2026 the full row ran on the AMD Radeon Pro 5500M of a 2019 16-inch MacBook Pro, driven through Apple's Metal Performance Shaders by PyTorch 2.2.2, the last PyTorch built for Intel Macs. Compute, checksum and memory were clean: 300 compute runs at three shapes in fp32 and fp16 inside their bounds and bit-identical, 80 checksum-protected runs with every checksum consistent, and 1,006,632,960 memory words read back correctly. Every self-test caught every planted fault: 30 of 30 in the compute probe, 12 of 12 located to their row and column in the checksum probe, 84 of 84 in the pattern probe, 30 of 30 flips located in memory, and 30 of 30 in the kernel probe. bf16, int8 and FP8 do not exist on this backend and were skipped.

Six steps sat outside their proven bound, and in every one of them all runs agreed with every other, so the repeat check found nothing to report. From the result files, radeon5500m_kernels.jsonl for the first four steps and radeon5500m_patterns.jsonl for the two matrix multiplies:

StepRuns outside the boundWorst error over boundWhat else the probe measured
RoPE fp3225 of 25120.6 times the boundthe backend's own sin and cos: 1.22e-4 absolute error, against the model's allowance of 9.5e-7 (2−20)
RoPE fp1625 of 257.6 timesthe same sin and cos measurement
GELU fp1625 of 252.05 timesthe backend's own erf: 1.9e-7 absolute error, inside the allowance
Layer norm fp1625 of 251.85 timeslayer norm fp32 on the same inputs: 0.0008 of its bound
Matrix multiply fp16, wide-exponent inputs, 1024×1024×102420 of 201.15 timesthe same inputs at 32×4096×11008: 0.18 of the bound; uniform inputs at 1024×1024×1024 in the compute probe: 0.43
Matrix multiply fp16, sparse inputs, 1024×1024×102420 of 201.57 timesthe same inputs at 32×4096×11008: 0.33 of the bound

What that does and does not say

The verdict word for these steps, outside-error-bound, is defined on the method page as either a faulty unit or a device that does not follow the arithmetic model every bound is derived from. The model allows exp, divide and square roots eight ulps and erf, sin and cos 2−20 absolute, and assumes intermediates are kept in FP32.

For RoPE the probe measured the cause directly: the backend's sin and cos are 128 times less accurate than the model allows, so the rotary embedding cannot land inside a bound built on that allowance, and the chip is not what the measurement is about. Metal's fast math is the likely reason, but the probe did not measure that; it measured the error of the functions as PyTorch exposes them.

For GELU fp16 the erf was measured inside its allowance, so the excess is not the erf. For layer norm fp16, and for the two fp16 matrix multiplies, nothing was measured beyond the error itself. The pattern that the same fp16 inputs pass at the thin shape, that uniform inputs pass at the square shape, and that every fp32 matrix multiply, layer norm and GELU step passes, is consistent with those fp16 kernels keeping intermediates below FP32 on this backend, and is not consistent with a single faulty unit, which would not choose its shape. That is a reading of the evidence, not a measurement, and the finding stays open until a build of PyTorch newer than 2.2.2 can be run on the same GPU, which Intel Macs cannot do.

How the tester reports it

The home page and the chip page count these 140 results separately from silent faults, which are runs that differed from their own repeat, checksums that did not agree, memory words that came back wrong, or results under load that differed from the verified first one. Both counts are read from the result files, and the row stays in the table with its six failing steps visible, because a yardstick that dropped inconvenient rows would be worth nothing.

Where it stands

Recorded in results/radeon5500m_kernels.jsonl and results/radeon5500m_patterns.jsonl. This is the first published row on which the kernel probe's own measurement of the backend's function library has fired, and it did what it was built for: it separated the library from the chip.

The chip page · The kernel result file · The pattern result file · The kernel probe · The pattern probe