syndrome · quantum error correction
IBM plans to build fault-tolerant quantum computers on bivariate bicycle codes such as the [[144,12,12]] gross code, decoded by Relay-BP, IBM's own algorithm. syndrome is an open GPU implementation of it, checked against IBM's reference code shot by shot: every decoding, every posterior in float64 and float32, every iteration count. On a T4 it decodes a single shot 28.7× faster than IBM's decoder, and a batch 4.0× faster than IBM's decoder on the whole CPU of the same machine.
$ python bench/m2_gpu.py # Kaggle, Tesla T4
float64, float32 · thread and warp per check
p = 0.003, 0.004 identical: 256 / 256
max |posterior difference| = 0.0
1,024 shots, float32, warp per check
syndrome, T4 199 ms per shot
IBM, all 4 threads 790 ms per shot → 4.0×
observables differing: 0 / 1,024A quantum computer corrects its errors only if a classical decoder works out what went wrong, round after round, as fast as the hardware produces syndromes: about once a microsecond on superconducting devices.
Bivariate bicycle codes protect 12 logical qubits with 144 data qubits at distance 12, far fewer qubits than a surface code needs for the same protection.
Relay-BP (Müller et al., arXiv:2506.01779) runs belief propagation in legs, each with different memory strengths, relaying posteriors from leg to leg until enough solutions are found.
IBM's open reference runs on a CPU: 1.65 s per shot at p = 0.3% and 5.1 to 5.5 s at p = 0.4% on one core here. NVIDIA's GPU Relay-BP is closed source. No open GPU implementation had been checked against IBM's.
Before any GPU number was reported, IBM's own decoder ran on IBM's own circuit files (gross code, Z memory, 12 rounds, uniform circuit noise), with the settings from IBM's results file.
| Shots | Logical errors | Rate | 95% interval | |
|---|---|---|---|---|
| IBM's published row, p = 0.4% | 10,000 | 44 | 4.4 × 10⁻³ | |
| IBM's decoder, run here | 10,000 | 41 | 4.1 × 10⁻³ | 2.9–5.6 × 10⁻³ |
The two counts differ by less than their sampling error (two-proportion z = −0.33). Raw results: results/m0/m0_reference_p0.004_Z.csv.
The same shots and the same table of memory strengths went through IBM's Rust decoder on the CPU and through syndrome's CUDA kernel on a T4, and every field was compared exactly, not within a tolerance.
| Messages | Schedule | p = 0.3% | p = 0.4% | Largest difference | Legs used |
|---|---|---|---|---|---|
| float64 vs IBM's RelayDecoderF64 | thread per check | 32/32 | 32/32 | 0.0 | 5–301 |
| float64 vs IBM's RelayDecoderF64 | warp per check | 32/32 | 32/32 | 0.0 | 5–301 |
| float32 vs IBM's RelayDecoderF32 | thread per check | 32/32 | 32/32 | 0.0 | 5–301 |
| float32 vs IBM's RelayDecoderF32 | warp per check | 32/32 | 32/32 | 0.0 | 5–301 |
A shot counts only if its decoding, every posterior, the success flag and the iteration count all equal IBM's. The CPU build of the same source also matches IBM's float32 decoder on 128 of 128 shots. Raw output: results/m2/m2_t4_f64_f32_kaggle_log.jsonl.
1,024 shots at p = 0.3% on the T4, and the same 1,024 shots through IBM's decoder in its parallel batch mode on all 4 CPU threads of the same Kaggle machine. Both predicted the same logical observables on every shot.
| Messages | IBM, all 4 threads | syndrome, thread per check | syndrome, warp per check | vs IBM |
|---|---|---|---|---|
| float64 | 1,051 ms per shot | 453 ms per shot | 298 ms per shot | 3.5× |
| float32 | 790 ms per shot | 330 ms per shot | 199 ms per shot | 4.0× |
How: float32 messages, matching IBM's own float32 decoder bit for bit (a T4 runs float32 32× faster than float64); one warp of 32 threads per check, reading a check's ~226 edges side by side and combining the two smallest magnitudes with shuffles, which changes no bits; and a shared queue, so a shot needing all 301 legs no longer holds back the rest. Together 2.0× faster per shot than the first GPU build. Against one CPU thread the float32 kernel is 6.2× faster. Raw output: results/m2/m2_t4_f64_f32_kaggle_log.jsonl.
Latency mode decodes one shot at a time with every thread of the GPU, held in step by grid-wide barriers. IBM's decoder works on one shot with one CPU thread, so that is the comparison, on the same 64 shots per row, each checked bit for bit.
| Messages | p | Identical to IBM | GPU, median per shot | IBM, median per shot | Speed-up |
|---|---|---|---|---|---|
| float32 | 0.3% | 64/64 | 37.5 ms | 1,078 ms | 28.7× |
| float32 | 0.4% | 64/64 | 75.9 ms | 2,057 ms | 27.1× |
| float64 | 0.3% | 64/64 | 76.8 ms | 1,208 ms | 15.7× |
| float64 | 0.4% | 64/64 | 176.7 ms | 2,744 ms | 15.5× |
About 0.10 ms per iteration over the gross code's 391,320 edges in float32. Two grid barriers per iteration: the satisfied-check of one iteration shares a phase with the check update of the next, and the tie-break weight is summed in IBM's order from per-block lists built with warp ballots. Raw output: results/m3/m3_t4_latency_kaggle_log.jsonl.
Bit-for-bit agreement does not happen by accident in floating point; each of these was needed.
The check and variable updates are written once in a header that compiles for the CPU and a CUDA thread block, following IBM's code operation for operation.
Min-sum with the two smallest magnitudes, Mem-BP priors, sums in IBM's order, the float32 priors rounded exactly as IBM's, and fused multiply-add turned off, because it rounds once where IBM's code rounds twice.
IBM's decoder draws memory strengths from one generator shared across shots, so its serial and parallel modes disagree on the same shots (reported to IBM's repository); syndrome uses IBM's explicit-table option so thousands of shots can be decoded at once and each compared exactly.
IBM's own decoder reproduced IBM's published error rate here before any GPU number was reported.
The speed is measured against a small 4-thread cloud CPU, on which IBM's batch mode gained only 1.6× over one thread; a large server would narrow the gap, and a per-watt comparison on matched hardware is still to do. Latency is now measured per shot, but 37.5 ms for 12 rounds is about 3 ms per round, far from the microsecond rounds of superconducting hardware; sliding-window decoding is next. The exact match is shown on 256 shots at two noise levels in one basis; GPU error rates over millions of shots, the X basis and streaming (sliding-window) decoding are still to do. An independent implementation, not affiliated with or endorsed by IBM.
@software{kundu2026syndrome,
author = {Kundu, Sankur},
title = {syndrome: an open GPU implementation of Relay-BP, bit-identical to IBM's reference},
year = {2026},
url = {https://github.com/Shakhtar-Sankur/syndrome},
license = {Apache-2.0}
}