Sankur Kundu

syndrome · quantum error correction

bit-identical to IBM.

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,024
256/256Shots where the GPU's decoding, posteriors, success and iterations equal IBM's, in float64 and float32
4.0×Faster than IBM's decoder on all 4 CPU threads of the same machine (float32, 1,024 shots)
28.7×Faster than IBM's decoder on a single shot: median 37.5 ms on the T4 against 1.08 s, every shot identical
41 vs 44Logical errors in 10,000 shots: IBM's decoder run here, against IBM's published count
01 · Why

A decoder has to keep up

A 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.

IBM's codes

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.

IBM's decoder

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.

The gap

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.

02 · Reproduce first

IBM's result, reproduced

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.

ShotsLogical errorsRate95% interval
IBM's published row, p = 0.4%10,000444.4 × 10⁻³
IBM's decoder, run here10,000414.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.

03 · Same answers

The GPU decodes as IBM's code does

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.

MessagesSchedulep = 0.3%p = 0.4%Largest differenceLegs used
float64 vs IBM's RelayDecoderF64thread per check32/3232/320.05–301
float64 vs IBM's RelayDecoderF64warp per check32/3232/320.05–301
float32 vs IBM's RelayDecoderF32thread per check32/3232/320.05–301
float32 vs IBM's RelayDecoderF32warp per check32/3232/320.05–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.

04 · Speed

4.0× IBM's decoder on the whole CPU

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.

MessagesIBM, all 4 threadssyndrome, thread per checksyndrome, warp per checkvs IBM
float641,051 ms per shot453 ms per shot298 ms per shot3.5×
float32790 ms per shot330 ms per shot199 ms per shot4.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.

05 · Latency

One shot, 28.7× faster than IBM's decoder

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.

MessagespIdentical to IBMGPU, median per shotIBM, median per shotSpeed-up
float320.3%64/6437.5 ms1,078 ms28.7×
float320.4%64/6475.9 ms2,057 ms27.1×
float640.3%64/6476.8 ms1,208 ms15.7×
float640.4%64/64176.7 ms2,744 ms15.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.

What syndrome adds

How the match was made

Bit-for-bit agreement does not happen by accident in floating point; each of these was needed.

  1. One source, two targets

    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.

  2. Same rounding, both precisions

    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.

  3. Independent shots

    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.

  4. Reproduced before compared

    IBM's own decoder reproduced IBM's published error rate here before any GPU number was reported.

Where it falls short

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}
}
Sankur Kundu

Sankur Kundu

ML systems engineer & independent researcher · Bhubaneswar, India · sankur.kundu.tw@gmail.com

Builds GPU and ML systems from scratch and checks them against the published state of the art. Also the author of kessler, whose collision-probability Monte Carlo matches NASA CARA's on all 53 of its test cases; tessel, a tile-language compiler with GEMM at cuBLAS parity; and lipi, a tokenizer that needs fewer tokens than GPT-4o's and Gemini's in 23 of 24 Indian languages. Fixes merged into Apple's MLX, Meta's ExecuTorch, Hugging Face's OpenEnv and Oxford's PyZX.