Skip to content

chore(dev): pin tsim-bench with call sizes that fit a GPU - #292

Merged
rafaelha merged 1 commit into
mainfrom
chore/bench-gpu-call-size
Nov 11, 2026
Merged

rafaelha merged 1 commit into
mainfrom
chore/bench-gpu-call-size

Conversation

@rafaelha

@rafaelha rafaelha commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

tsim-dev bench --device gpu reported GPU throughput about 3.7x too low. The tsim-bench worker capped one sampling call at 2^28 / outputs shots, as if every shot came back as unpacked booleans. tsim-dev bench asks for bit-packed rows, though, and a postselected call only returns the rows it keeps. On end2end_d5_dsurface15_p0.001 (2745 outputs, 5.2% kept) the cap allowed 97,790 shots per call, and the CUDA sampler's fixed ~0.2 ms per call dominated.

The served (non---thorough) rounds had a second problem: the first side's worker calibrated one call and every side timed it. With a GPU side against a CPU side, the CPU then had to run a GPU-sized call, which takes minutes.

Fix

  • Re-pin tsim-bench to worker: bound a call by the bytes it returns; warm the timed size rafaelha/tsim-bench#2 (19804f26). Its worker bounds a call by the bytes it actually returns: ceil(outputs/8) per bit-packed row, times the acceptance measured during calibration. The returned array stays under 256 MB, and a call is also capped at 2^27 shots. The timed size gets two warm calls before timing, because the first calls at a new size are slow on a GPU (pinned host buffers).
  • _served_rounds calibrates once per device, so sides on different devices each time a call of their own size. There is a unit test for this.
  • The benchmarking skill notes both changes.

The new pin also brings in tsim-bench 9bc81ad (qv_q10: one observable per measured qubit). That changes what qv_q10 measures, so its before/after rates below are not like-for-like.

Numbers (RTX 5090, 1 CPU thread)

GPU side: the CUDA branch simplify/cuda@70e55b89 (#219), since main has no GPU backend.

tsim-dev bench end2end_d5_dsurface15_p0.001 --side <cuda>:device=gpu --side <cuda>:device=cpu --thorough:

tsim-bench GPU shots/s shots per call CPU shots/s GPU vs CPU
eb4a6d11 (old pin) 512M 97,790 0.86M 595x
19804f26 (new pin) 1.89G 15.1M 0.86M 2196x

The default served mode now gives the same result: 1.89G against 0.86M, 2197x.

GPU quick suite (--side <cuda>:device=gpu), old pin → new pin: inject_cultivate_d3 12.6G → 14.8G, inject_cultivate_d5 4.36G → 6.89G, end2end_d3 242M → 349M, hook_inject_T_d15 139M → 178M, distillation_color_d5_Z 14.7G → 19.6G, coherent_d3_r3 8.77G → 10.0G, surface_d7_r7 1.08G → 1.23G, qv_q10 197M → 195M.

CPU quick suite (--side .:device=cpu, Tsim main), old pin / new pin (CPU calls are bounded by time, not by the cap, so the rates should not move):

circuit old new
inject_cultivate_d3_p0.001 28.5M 28.7M
inject_cultivate_d5_p0.001 14.8M 14.8M
end2end_d3_dsurface15_p0.001 1.07M 1.07M
hook_inject_T_d15_pd5_p0.001 1.11M 1.10M
distillation_color_d5_Z 12.9M 13.0M
coherent_d3_r3 19.6M 19.7M
surface_d7_r7 13.3M 13.3M
qv_q10 10.7M 10.7M

uv run tsim-dev check passes (lint, 2618 tests), and python -m tsim_bench check reports 0 failed.

Merge order

rafaelha/tsim-bench#2 must merge first. REV pins its branch commit 19804f26, which _export can fetch because it lives on a pushed branch. If that PR is squash-merged, re-pin REV here to the merged commit on tsim-bench main before merging.

🤖 Generated with Claude Code

tsim-bench's worker capped a call at 2^28 / outputs shots, ignoring bit-packing
and postselection, so on a GPU the per-call overhead dominated (512M instead of
1.9G shots/s on end2end_d5_dsurface15_p0.001). The new pin bounds a call by the
bytes it returns and warms the timed size. Served rounds now calibrate once per
device, since a GPU's call would take minutes on a CPU.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-11T17:15:46.984264Z 0a317f8 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0a317f8560

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread dev/tsim_dev/bench.py
Comment on lines +557 to +558
device = side.device or args.device
if device not in calibrated:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Calibrate from the device each worker actually selected

When both sides request device=gpu but one checkout lacks a GPU backend or falls back to CPU, both are grouped under calibrated["gpu"] even though they execute on different devices. The second side then inherits the first side’s call size: CPU-first calibration can understate GPU throughput due to per-call overhead, while GPU-first calibration can give the CPU an extremely long call and time out—the same failure this change intends to prevent. Group by the worker’s actual reported device, or calibrate each side independently.

AGENTS.md reference: AGENTS.md:L95-L98

Useful? React with 👍 / 👎.

@rafaelha
rafaelha merged commit 41479a1 into main Nov 11, 2026
8 of 10 checks passed
@rafaelha
rafaelha deleted the chore/bench-gpu-call-size branch November 11, 2026 19:12
rafaelha added a commit that referenced this pull request Nov 12, 2026
Stacked on #284. On an Apple-silicon Mac,
`strategy="active_statevector"` now samples on the GPU through Metal
whenever every component has at most 11 active qubits. A plain install
includes it: Tsim depends on `pyobjc-framework-Metal` (about 7 MB) on
Apple silicon only, so other platforms install nothing new.

## What users get
- **Automatic selection:** no new argument.
- The samplers fall back to the CPU, with a warning, when a component is
wider than 11 qubits, the noise is beyond the GPU tables' limits, the
GPU is out of memory, a kernel does not compile, or the process was
forked after its parent used the GPU. A GPU whose SIMD lanes do not
follow thread order (checked by a probe kernel) is not used at all.
- The repr says where a sampler samples (`samples on metal`), and so
does `sampler.stats.device`.
- **Opt-out:** `TSIM_DEVICE=cpu` keeps the samplers on the CPU, in
double precision. Tsim reads it each time it compiles a sampler.
- **Precision and seeds:** the GPU computes outcome probabilities in
float32 and never draws an outcome below about 1e-11. A seed's GPU
samples differ from the CPU's and depend on `batch_size`. The install
page, the docstrings, the user skill, AGENTS.md and the CHANGELOG say
so.

## How it works
- **Kernels:** the noise sampler and every component's tape run as Metal
kernels that PyObjC compiles at runtime. Metal caches them across
processes.
- The tapes come from #284's shared generator, with a Metal dialect: no
`goto`, SIMD-group shuffles, and amplitudes in arrays of 32. Metal moves
an array of 64 to the stack, which costs 1.8×.
- The rarer outcome is drawn against its own share, in float-float,
since Metal has no double.
  - Noise below p ≈ 1e-12 per segment takes a 48-bit fine path.
- **Rows:** postselection, row packing and the boolean unpack all run on
the GPU. Buffers live in unified memory, so nothing is copied, and freed
output buffers are reused.
- **Wide rows:** rows over 47 words keep their noise words in device
memory.

## Benchmark
Every tsim-bench circuit that Metal runs (179 circuits with k ≤ 11, from
28 papers), Metal against one CPU core of the same Mac:
`tsim-dev bench --suite corpus --max-width 11 --side .:device=gpu --side
.:device=cpu,threads=1` at `58471a37`, which includes the harness fix
from #292 (each device calibrates its own call size). Apple M4 Pro,
20-core GPU, macOS 27.2. Detector sampler, bit-packed, each circuit
postselected as its paper does; the rate counts requested shots, kept or
not.

- **All 179 run on the GPU**, none failed. Speedup over one core: median
**43×**, geometric mean **39×**, range 0.81–835×. 12 circuits are above
200×, 61 at 50–200×, 80 at 10–50×, 22 at 1–10×, and only `qv_q10` is
below 1×.

| circuit | k | kept | Metal | 1 CPU core | Metal ÷ 1 core |
|---|--:|--:|--:|--:|--:|
| **end2end_d5_dsurface15_p0.001** (cultivation d=5, end to end) | 6 |
0.052 | 313M | 1.37M | **229×** |
| end2end_d3_dsurface15_p0.001 | 4 | 0.54 | 163M | 1.65M | 99× |
| inject_cultivate_d3_p0.001 | 4 | 0.69 | 1.98G | 36.9M | 54× |
| inject_cultivate_d5_p0.001 | 6 | 0.14 | 748M | 20.0M | 37× |
| cult_growing_T_d5_to_d13_p0.001 | 6 | 0.12 | 351M | 1.99M | 176× |
| gauging_cultivation_d7_T_p0.001 | 8 | 0.00023 | 435M | 4.22M | 103× |
| distillation_color_d5_Z | 3 | 0.080 | 2.55G | 25.0M | 102× |
| bb144_cfg369_inj1_T_p0.001 (qLDPC injection) | 1 | 0.070 | 1.32G |
8.54M | 155× |
| hook_inject_T_d15_pd5_p0.001 | 1 | 0.67 | 62.4M | 1.76M | 35× |
| surface_d7_r7 | 0 | – | 757M | 21.9M | 35× |
| coherent_d3_r3 | 4 | – | 1.16G | 26.6M | 44× |
| coherent_d5_r5 | 11 | – | 467k | 3.0k | 156× |
| MSC_circuit_d7_p0.0005 | 8 | – | 31.4M | 56.1k | 560× |
| stability_d3_X_t20_alpha1.0_p0.001 | 3 | – | 133M | 203k | 656× |
| bh40-T (Magna) | 3 | 0.88 | 757M | 920k | 823× |
| distillation_15to1_T_d25_p0.001 | 4 | – | 606k | 66.8k | 9.1× |
| rotated-init-x-injection-double-HXY_d1=3_p=0.001 | 8 | 0.62 | 64.1M |
12.4M | 5.2× |
| coherent_d3_r1 | 0 | – | 8.19G | 2.17G | 3.8× |
| tricycle_6_3_2 | 9 | 0.92 | 17.5M | 5.16M | 3.4× |
| star_d=11 | 2 | 0.00095 | 3.58M | 1.36M | 2.6× |
| tricycle_9_3_2 | 11 | 0.87 | 757k | 378k | 2.0× |
| qv_q10 | 10 | – | 11.8M | 14.6M | 0.81× |

- **Largest gains** come from circuits whose cost is mostly noise
sampling and postselection (the Magna `bh*-T` distillation, stability
experiments, end-to-end cultivation): those parallelize across the GPU.
- **Smallest gains** come from wide components without much noise
(`qv_q10`, `tricycle_*`, k = 9–11), where every shot is limited by the
GPU's arithmetic, and where the CPU shares trajectories between shots
that draw the same outcomes. A component of k = 9 runs at about 58 ns
per shot, close to the M4 Pro's FMA and shuffle throughput.
- **Compile:** Metal adds about 0.1–0.5 s per process: about 0.1 s for
PyObjC and the device, plus building the GPU sampler.
- The machine had light other load (3–4 busy cores) during the run. Each
ratio is the mean of 3–5 interleaved rounds; the CPU column is Metal's
rate divided by it.

<details><summary>All 179 circuits</summary>

| Paper | Circuit | k | Kept | Metal (shots/s) | 1 CPU core (shots/s) |
Metal ÷ 1 core |
|---|---|--:|--:|--:|--:|--:|
| 1811.12926_quantum_volume | `qv_q10` | 10 | - | 11.8 M | 14.6 M |
0.81× |
| 2302.12292_hook_injection | `hook_inject_T_d15_pd5_p0.001` | 1 | 0.667
| 62.4 M | 1.76 M | 35.4× |
| 2302.12292_hook_injection | `hook_inject_T_d15_pd7_p0.001` | 1 | 0.45
| 66.3 M | 1.7 M | 39× |
| 2302.12292_hook_injection | `li_inject_T_d15_pd5_p0.001` | 1 | 0.457 |
29.6 M | 891 k | 33.2× |
| 2302.12292_hook_injection | `li_inject_T_d15_pd7_p0.001` | 1 | 0.196 |
30.3 M | 853 k | 35.5× |
| 2302.12292_hook_injection | `pregrown_hook_inject_T_d15_pd5_p0.001` |
1 | 0.587 | 53.5 M | 1.56 M | 34.4× |
| 2302.12292_hook_injection | `pregrown_hook_inject_T_d15_pd7_p0.001` |
1 | 0.377 | 57.6 M | 1.56 M | 36.9× |
| 2302.12292_hook_injection | `zz_inject_T_d15_pd5_p0.001` | 1 | 0.531 |
26.5 M | 830 k | 31.9× |
| 2302.12292_hook_injection | `zz_inject_T_d15_pd7_p0.001` | 1 | 0.269 |
27.3 M | 832 k | 32.8× |
| 2406.17653_transversal_algorithmic_ft |
`s_injection_T_d3_p0.001_pinj0.1` | 1 | 0.912 | 3.97 G | 55.8 M | 71.1×
|
| 2406.17653_transversal_algorithmic_ft |
`s_injection_T_d5_p0.001_pinj0.1` | 1 | 0.912 | 2.45 G | 45.7 M | 53.6×
|
| 2406.17653_transversal_algorithmic_ft |
`s_injection_T_d7_p0.001_pinj0.1` | 1 | 0.912 | 1.08 G | 35.7 M | 30.2×
|
| 2406.17653_transversal_algorithmic_ft |
`s_injection_T_d9_p0.001_pinj0.1` | 1 | 0.912 | 711 M | 26.4 M | 27× |
| 2409.07707_color_code_msd | `cult_growing_T_d3_to_d11_p0.001` | 4 |
0.644 | 296 M | 3.63 M | 81.5× |
| 2409.07707_color_code_msd | `cult_growing_T_d3_to_d7_p0.001` | 4 |
0.644 | 845 M | 11.5 M | 73.3× |
| 2409.07707_color_code_msd | `cult_growing_T_d5_to_d13_p0.001` | 6 |
0.12 | 351 M | 1.99 M | 176× |
| 2409.07707_color_code_msd | `cult_growing_T_d5_to_d9_p0.001` | 6 |
0.12 | 584 M | 5.42 M | 108× |
| 2409.17595_magic_state_cultivation | `end2end_d3_dsurface15_p0.001` |
4 | 0.539 | 163 M | 1.65 M | 98.6× |
| 2409.17595_magic_state_cultivation | `end2end_d5_dsurface15_p0.001` |
6 | 0.0519 | 313 M | 1.37 M | 229× |
| 2409.17595_magic_state_cultivation | `inject_cultivate_d3_p0.001` | 4
| 0.687 | 1.98 G | 36.9 M | 53.7× |
| 2409.17595_magic_state_cultivation | `inject_cultivate_d5_p0.001` | 6
| 0.144 | 748 M | 20 M | 37.3× |
| 2502.01743_surface_code_cultivation |
`expanded-rotated-init-x-injection-double-HXY_d1=3_d2=11_p=0.001` | 8 |
0.648 | 46.9 M | 2.9 M | 16.1× |
| 2502.01743_surface_code_cultivation |
`expanded-rotated-injection-double-ghz-HXY_d1=3_d2=11_p=0.001` | 8 |
0.626 | 52.1 M | 2.97 M | 17.6× |
| 2502.01743_surface_code_cultivation |
`expanded-rotated-injection-syndrome-double-ghz-HXY_d1=3_d2=11_p=0.001`
| 8 | 0.573 | 57.9 M | 3.03 M | 19.1× |
| 2502.01743_surface_code_cultivation |
`expanded-unrotated_H_injection-ghz_d1=3_d2=11_p=0.001` | 9 | 0.689 |
11.6 M | 223 k | 52.1× |
| 2502.01743_surface_code_cultivation |
`expanded-unrotated_H_injection-syndrome-ghz_d1=3_d2=11_p=0.001` | 9 |
0.538 | 14.6 M | 380 k | 38.4× |
| 2502.01743_surface_code_cultivation |
`rotated-init-x-injection-double-HXY_d1=3_p=0.001` | 8 | 0.624 | 64.1 M
| 12.4 M | 5.15× |
| 2502.01743_surface_code_cultivation |
`rotated-init-x-injection-syndrome-double-ghz-HXY_d1=3_p=0.001` | 8 |
0.571 | 70.2 M | 13.2 M | 5.3× |
| 2502.01743_surface_code_cultivation |
`rotated-injection-double-ghz-HXY_d1=3_p=0.001` | 8 | 0.603 | 65.5 M |
11.8 M | 5.55× |
| 2502.01743_surface_code_cultivation |
`rotated-injection-ghz-HXY_d1=3_p=0.001` | 6 | 0.558 | 210 M | 19.4 M |
10.8× |
| 2502.01743_surface_code_cultivation |
`rotated-injection-syndrome-double-ghz-HXY_d1=3_p=0.001` | 8 | 0.552 |
73.2 M | 13.2 M | 5.54× |
| 2502.01743_surface_code_cultivation |
`unrotated_HXY_injection-ghz_d1=3_p=0.001` | 7 | 0.561 | 136 M | 14.9 M
| 9.15× |
| 2502.01743_surface_code_cultivation |
`unrotated_HXY_injection-syndrome-ghz_d1=3_p=0.001` | 7 | 0.474 | 179 M
| 17.4 M | 10.3× |
| 2502.01743_surface_code_cultivation |
`unrotated_H_injection-ghz_d1=3_p=0.001` | 9 | 0.548 | 17.8 M | 646 k |
27.6× |
| 2502.01743_surface_code_cultivation |
`unrotated_H_injection-syndrome-ghz_d1=3_p=0.001` | 9 | 0.463 | 24.2 M |
1.4 M | 17.3× |
| 2503.18657_rp2_cultivation | `rp_3_T_cult` | 4 | 0.514 | 1.7 G | 27 M
| 63.1× |
| 2503.18657_rp2_cultivation | `rp_3_rp_5_T_cult` | 10 | 0.0945 | 44 M |
7.97 M | 5.52× |
| 2503.18657_rp2_cultivation | `rp_3_sc_7_end2end_4_full_rds` | 4 |
0.554 | 962 M | 13.9 M | 69.2× |
| 2503.18657_rp2_cultivation | `rp_3_rp_5_sc_11_end2end_6_full_rds` | 9
| 0.116 | 73.9 M | 909 k | 81.3× |
| 2504.02935_erasure_injection | `hook_inject_T_d3_p0.001` | 1 | 0.902 |
4.35 G | 83.7 M | 52× |
| 2504.02935_erasure_injection | `inject_cultivate_T_d3_p0.001` | 4 |
0.687 | 2.57 G | 42.2 M | 61× |
| 2505.13587_correlated_decoding_distillation |
`distillation_15to1_T_d5_p0.001` | 4 | - | 49.1 M | 2.03 M | 24.2× |
| 2505.13587_correlated_decoding_distillation |
`distillation_15to1_T_d9_p0.001` | 4 | - | 8.94 M | 634 k | 14.1× |
| 2505.13587_correlated_decoding_distillation |
`distillation_15to1_T_d13_p0.001` | 4 | - | 3.4 M | 289 k | 11.8× |
| 2505.13587_correlated_decoding_distillation |
`distillation_15to1_T_d17_p0.001` | 4 | - | 1.68 M | 155 k | 10.9× |
| 2505.13587_correlated_decoding_distillation |
`distillation_15to1_T_d21_p0.001` | 4 | - | 962 k | 102 k | 9.41× |
| 2505.13587_correlated_decoding_distillation |
`distillation_15to1_T_d25_p0.001` | 4 | - | 606 k | 66.8 k | 9.07× |
| 2507.12511_unfolded_distillation |
`unfolded_repetition_no_preselection_pz1e-3_rounds2` | 4 | - | 1.89 G |
37.3 M | 50.7× |
| 2507.12511_unfolded_distillation |
`unfolded_repetition_no_preselection_pz1e-3_rounds4` | 4 | - | 1.33 G |
24.1 M | 55.2× |
| 2507.12511_unfolded_distillation |
`unfolded_repetition_no_preselection_pz1e-3_rounds6` | 4 | - | 1.13 G |
18.4 M | 61.6× |
| 2507.12511_unfolded_distillation |
`unfolded_surface_no_bitflip_pz1e-3_rounds2` | 4 | 1 | 1.07 G | 24.4 M |
43.9× |
| 2507.12511_unfolded_distillation |
`unfolded_surface_no_bitflip_pz1e-3_rounds4` | 4 | 1 | 637 M | 15.5 M |
41.1× |
| 2507.12511_unfolded_distillation |
`unfolded_surface_no_bitflip_pz1e-3_rounds6` | 4 | 1 | 456 M | 11.2 M |
40.6× |
| 2509.05212_fold_transversal_cultivation | `fold-d3_native_T_p0.001` |
6 | 0.75 | 219 M | 32.7 M | 6.69× |
| 2509.05212_fold_transversal_cultivation | `fold-d5_native_T_p0.001` |
8 | 0.16 | 56.5 M | 5.23 M | 10.8× |
| 2509.08658_few_clifford_terms | `d3_cultivation_T_p0.001` | 4 | 0.647
| 2.59 G | 40.7 M | 63.7× |
| 2509.18294_transversal_star | `star_d=3` | 2 | 0.679 | 3.02 G | 42.1 M
| 71.7× |
| 2509.18294_transversal_star | `star_d=5` | 2 | 0.237 | 1.32 G | 22 M |
60× |
| 2509.18294_transversal_star | `star_d=7` | 2 | 0.051 | 987 M | 12.4 M
| 79.8× |
| 2509.18294_transversal_star | `star_d=9` | 2 | 0.00791 | 761 M | 6.77
M | 112× |
| 2509.18294_transversal_star | `star_d=11` | 2 | 0.000949 | 3.58 M |
1.36 M | 2.64× |
| 2509.18294_transversal_star | `star_d=13` | 2 | 9.21e-05 | 2.34 M |
830 k | 2.82× |
| 2509.18294_transversal_star | `star_d=15` | 2 | 7e-06 | 76.8 M | 1.65
M | 46.4× |
| 2510.07269_transversal_dimension_jump | `tricycle_6_3_2` | 9 | 0.92 |
17.5 M | 5.16 M | 3.39× |
| 2510.07269_transversal_dimension_jump | `tricycle_9_3_2` | 11 | 0.867
| 757 k | 378 k | 2× |
| 2510.24615_lattice_surgery_cultivation | `inject-check-3_T` | 4 | 0.73
| 2.23 G | 45 M | 49.6× |
| 2510.24615_lattice_surgery_cultivation |
`lattice-surgery-complementary-gap-3-to-5-to-7_T` | 4 | 0.574 | 1.39 G |
19.6 M | 70.8× |
| 2512.13908_google_cultivation_experiment | `fig2_cultivation_stim_X_T`
| 4 | 1 | 5.04 G | 99.8 M | 50.5× |
| 2512.13908_google_cultivation_experiment | `fig2_cultivation_stim_Z_T`
| 4 | 1 | 5.04 G | 103 M | 48.7× |
| 2512.13908_google_cultivation_experiment | `fig2_injection_stim_X_T` |
1 | 1 | 5.41 G | 117 M | 46.1× |
| 2512.13908_google_cultivation_experiment | `fig2_injection_stim_Z_T` |
1 | 1 | 5.4 G | 117 M | 46.1× |
| 2512.13908_google_cultivation_experiment | `fig2_simulation_stim_X_T`
| 4 | 1 | 5.06 G | 100 M | 50.6× |
| 2512.13908_google_cultivation_experiment | `fig2_simulation_stim_Z_T`
| 4 | 1 | 5.07 G | 105 M | 48.4× |
| 2512.13908_google_cultivation_experiment | `fig3_injection_stim_T` | 4
| 1 | 2.26 G | 102 M | 22.1× |
| 2512.13908_google_cultivation_experiment | `fig3_simulation_stim_T` |
4 | 1 | 1.2 G | 98.6 M | 12.2× |
| 2512.13908_google_cultivation_experiment |
`fig3_simulation_zoom_stim_T` | 4 | 1 | 1.2 G | 99.8 M | 12× |
| 2512.13908_google_cultivation_experiment |
`fig3_with_idle_rounds_stim_T` | 4 | 1 | 1.2 G | 100 M | 11.9× |
| 2512.13908_google_cultivation_experiment |
`fig3_with_idle_rounds_zoom_stim_T` | 4 | 1 | 1.2 G | 98.9 M | 12.1× |
| 2512.13908_google_cultivation_experiment |
`fig3_without_idle_rounds_stim_T` | 4 | 1 | 3.27 G | 103 M | 31.9× |
| 2512.13908_google_cultivation_experiment |
`fig4_cultivation_N2_stim_X_T` | 4 | 1 | 2.45 G | 95.2 M | 25.7× |
| 2512.13908_google_cultivation_experiment |
`fig4_cultivation_N2_stim_Z_T` | 4 | 1 | 2.43 G | 96 M | 25.3× |
| 2512.13908_google_cultivation_experiment |
`fig4_cultivation_N3_stim_X_T` | 4 | 1 | 2.95 G | 95.3 M | 31× |
| 2512.13908_google_cultivation_experiment |
`fig4_cultivation_N3_stim_Z_T` | 4 | 1 | 3.05 G | 96.2 M | 31.7× |
| 2512.13908_google_cultivation_experiment |
`fig4_injection_N2_stim_X_T` | 1 | 1 | 4.65 G | 117 M | 39.7× |
| 2512.13908_google_cultivation_experiment |
`fig4_injection_N2_stim_Z_T` | 1 | 1 | 4.65 G | 118 M | 39.5× |
| 2512.13908_google_cultivation_experiment |
`fig4_simulation_injection_N2_stim_X_T` | 1 | 1 | 4.68 G | 117 M | 39.9×
|
| 2512.13908_google_cultivation_experiment |
`fig4_simulation_injection_N2_stim_Z_T` | 1 | 1 | 4.67 G | 117 M | 39.9×
|
| 2512.23037_soft | `circuit_d3_p0.001` | 4 | 0.687 | 1.87 G | 44.8 M |
41.7× |
| 2512.23037_soft | `circuit_d5_p0.001` | 6 | 0.144 | 976 M | 14.1 M |
69.1× |
| 2603.05429_gauging_clifford_cultivation |
`gauging_cultivation_d3_T_p0.001` | 4 | 0.461 | 1.89 G | 32 M | 59.1× |
| 2603.05429_gauging_clifford_cultivation |
`gauging_cultivation_d5_T_p0.001` | 6 | 0.0401 | 577 M | 10 M | 57.7× |
| 2603.05429_gauging_clifford_cultivation |
`gauging_cultivation_d7_T_p0.001` | 8 | 0.000225 | 435 M | 4.22 M | 103×
|
| 2604.01059_tsim | `distillation_color_d5_X` | 4 | 0.0797 | 2.73 G |
31.9 M | 85.6× |
| 2604.01059_tsim | `distillation_steane_d3_X` | 4 | 0.353 | 3.13 G |
43.6 M | 71.7× |
| 2604.01059_tsim | `distillation_unencoded_X` | 4 | - | 2.85 G | 58.5 M
| 48.7× |
| 2604.01059_tsim | `distillation_color_d5_Y` | 3 | 0.0874 | 2.78 G |
32.7 M | 84.9× |
| 2604.01059_tsim | `distillation_steane_d3_Y` | 3 | 0.367 | 3.36 G |
43.3 M | 77.5× |
| 2604.01059_tsim | `distillation_unencoded_Y` | 3 | - | 3.28 G | 58.7 M
| 55.9× |
| 2604.01059_tsim | `distillation_color_d5_Z` | 3 | 0.0798 | 2.55 G | 25
M | 102× |
| 2604.01059_tsim | `distillation_steane_d3_Z` | 3 | 0.353 | 2.79 G | 33
M | 84.5× |
| 2604.01059_tsim | `distillation_unencoded_Z` | 3 | - | 2.27 G | 41.6 M
| 54.6× |
| 2604.05126_qldpc_magic_state_injection | `bb144_cfg328_inj5_T_p0.001`
| 1 | 0.069 | 1.23 G | 7.79 M | 158× |
| 2604.05126_qldpc_magic_state_injection | `bb144_cfg342_inj3_T_p0.001`
| 1 | 0.0687 | 1.3 G | 8.19 M | 159× |
| 2604.05126_qldpc_magic_state_injection | `bb144_cfg369_inj1_T_p0.001`
| 1 | 0.0703 | 1.32 G | 8.54 M | 155× |
| 2604.05126_qldpc_magic_state_injection | `bb144_cfg422_inj2_T_p0.001`
| 1 | 0.0687 | 1.32 G | 8.21 M | 161× |
| 2604.05126_qldpc_magic_state_injection | `bb144_cfg491_inj4_T_p0.001`
| 1 | 0.0684 | 1.31 G | 8.11 M | 162× |
| 2604.05126_qldpc_magic_state_injection | `bb144_cfg88_inj6_T_p0.001` |
1 | 0.0721 | 1.29 G | 7.98 M | 162× |
| 2604.27058_clifft | `coherent_d3_r1` | 0 | - | 8.19 G | 2.17 G | 3.78×
|
| 2604.27058_clifft | `coherent_d3_r3` | 4 | - | 1.16 G | 26.6 M | 43.5×
|
| 2604.27058_clifft | `coherent_d5_r1` | 0 | - | 7.79 G | 857 M | 9.09×
|
| 2604.27058_clifft | `coherent_d5_r5` | 11 | - | 467 k | 3 k | 156× |
| 2604.27058_clifft | `cultivation_d3` | 4 | 0.687 | 2.04 G | 41.8 M |
48.8× |
| 2604.27058_clifft | `cultivation_d5` | 6 | 0.144 | 750 M | 20.3 M |
37× |
| 2604.27058_clifft | `distillation` | 3 | 0.0798 | 2.57 G | 24.7 M |
104× |
| 2604.27058_clifft | `surface_d7_r7` | 0 | - | 757 M | 21.9 M | 34.5× |
| 2607.28600_symft | `coherent_surface_d3_r1_p1e-3_rz0p02` | 0 | - |
8.19 G | 2.16 G | 3.8× |
| 2607.28600_symft | `coherent_surface_d3_r3_p1e-3_rz0p02` | 4 | - |
1.17 G | 27 M | 43.3× |
| 2607.28600_symft | `coherent_surface_d5_r1_p1e-3_rz0p02` | 0 | - |
7.78 G | 851 M | 9.14× |
| 2607.28600_symft | `coherent_surface_d5_r5_p1e-3_rz0p02` | 11 | - |
466 k | 3.04 k | 154× |
| 2607.28600_symft | `distillation` | 3 | 0.0798 | 2.56 G | 24.9 M |
103× |
| 2607.28600_symft | `msc_d3_inject_cultivate_p1e-3` | 4 | 0.687 | 2.01
G | 41.2 M | 48.8× |
| 2607.28600_symft | `msc_d5_inject_cultivate_p1e-3` | 6 | 0.144 | 767 M
| 20.3 M | 37.9× |
| 2607.28600_symft | `MSC_circuit_d7_p0.0005` | 8 | - | 31.4 M | 56.1 k
| 560× |
| 2607.28600_symft | `msc_proxy_d7_unverified_p1e-3` | 6 | - | 171 M |
673 k | 254× |
| 2607.28600_symft | `pure_surface_d7_r7_p1e-3` | 0 | - | 758 M | 21.9 M
| 34.5× |
| 2607.28600_symft | `pure_surface_d9_r9_p1e-3` | 0 | - | 386 M | 9.88 M
| 39.1× |
| 2609.17706_chan_color_code_cultivation |
`d3a6f2_inject_cultivate_T_p0.001` | 4 | 0.639 | 2.14 G | 40.5 M | 52.8×
|
| 2609.17706_chan_color_code_cultivation |
`d3a6_inject_cultivate_T_p0.001` | 4 | 0.687 | 2.05 G | 41.7 M | 49.2× |
| 2609.17706_chan_color_code_cultivation |
`d5a19f13_inject_cultivate_T_p0.001` | 6 | 0.106 | 825 M | 19.5 M |
42.4× |
| 2609.17706_chan_color_code_cultivation |
`d5a19_inject_cultivate_T_p0.001` | 6 | 0.144 | 651 M | 20.1 M | 32.4× |
| 2609.17706_chan_color_code_cultivation |
`d5a19r4f13_inject_cultivate_T_p0.001` | 6 | 0.0811 | 909 M | 17 M |
53.5× |
| 2609.17706_chan_color_code_cultivation |
`d5a19r4_inject_cultivate_T_p0.001` | 6 | 0.111 | 808 M | 19.2 M | 42.1×
|
| 2609.18922_exact_ler_cultivation | `clifft_d3_p001` | 4 | 0.687 | 1.99
G | 40.9 M | 48.6× |
| 2609.18922_exact_ler_cultivation | `soft_cultivation_d5_p0005` | 6 |
6.52e-05 | 868 M | 6.16 M | 141× |
| 2610.03869_modular_neural_decoder |
`stability_d3_X_t100_alpha0.5_p0.001` | 3 | - | 15.5 M | 63.5 k | 244× |
| 2610.03869_modular_neural_decoder |
`stability_d3_X_t20_alpha0.15_p0.001` | 3 | - | 145 M | 2.17 M | 66.8× |
| 2610.03869_modular_neural_decoder |
`stability_d3_X_t20_alpha0.5_p0.001` | 3 | - | 145 M | 469 k | 309× |
| 2610.03869_modular_neural_decoder |
`stability_d3_X_t20_alpha1.0_p0.001` | 3 | - | 133 M | 203 k | 656× |
| 2610.06811_merlin | `cultivation_d3_t` | 4 | 0.687 | 1.88 G | 41.9 M |
44.9× |
| 2610.06811_merlin | `cultivation_d5_t` | 6 | 0.144 | 750 M | 20.4 M |
36.8× |
| 2610.06811_merlin | `distillation_15to1_p0.001` | 4 | 0.985 | 4.72 G |
69.8 M | 67.6× |
| 2610.06811_merlin | `distillation_bh2_p0.001` | 4 | 0.986 | 4.74 G |
73.8 M | 64.2× |
| 2610.06811_merlin | `distillation_bh4_p0.001` | 4 | 0.98 | 1.42 G | 63
M | 22.5× |
| 2610.06811_merlin | `distillation_bh6_p0.001` | 4 | 0.974 | 2.13 G |
70.3 M | 30.3× |
| 2610.06811_merlin | `distillation_bh8_p0.001` | 4 | 0.969 | 950 M |
60.5 M | 15.7× |
| 2610.06811_merlin | `distillation_bh10_p0.001` | 4 | 0.963 | 1.17 G |
68.5 M | 17.1× |
| 2610.06811_merlin | `distillation_bh12_p0.001` | 4 | 0.957 | 697 M |
58.7 M | 11.9× |
| 2610.06811_merlin | `distillation_bh14_p0.001` | 4 | 0.951 | 790 M |
67.5 M | 11.7× |
| 2610.06811_merlin | `distillation_bh16_p0.001` | 4 | 0.946 | 559 M |
57.5 M | 9.73× |
| 2610.06811_merlin | `distillation_bh18_p0.001` | 4 | 0.94 | 657 M |
65.4 M | 10× |
| 2610.06828_practical_cost_cultivation | `color_code_cultivation_f3_T`
| 4 | 0.539 | 167 M | 1.66 M | 101× |
| 2610.06828_practical_cost_cultivation | `color_code_cultivation_f5_T`
| 6 | 0.0519 | 315 M | 1.4 M | 225× |
| 2610.22315_magna | `bh2-T` | 3 | 0.986 | 3.92 G | 62.2 M | 63× |
| 2610.22315_magna | `bh2-S` | 0 | 0.986 | 6.97 G | 990 M | 7.04× |
| 2610.22315_magna | `bh4-T` | 3 | 0.98 | 3.29 G | 52.3 M | 62.9× |
| 2610.22315_magna | `bh4-S` | 0 | 0.98 | 6.95 G | 913 M | 7.61× |
| 2610.22315_magna | `bh6-T` | 3 | 0.974 | 2.85 G | 44 M | 64.8× |
| 2610.22315_magna | `bh6-S` | 0 | 0.974 | 6.75 G | 859 M | 7.86× |
| 2610.22315_magna | `bh10-T` | 3 | 0.963 | 2.21 G | 26.2 M | 84.5× |
| 2610.22315_magna | `bh10-S` | 0 | 0.963 | 6.8 G | 809 M | 8.41× |
| 2610.22315_magna | `bh20-T` | 3 | 0.935 | 1.32 G | 1.75 M | 756× |
| 2610.22315_magna | `bh20-S` | 0 | 0.935 | 6.63 G | 612 M | 10.8× |
| 2610.22315_magna | `bh40-T` | 3 | 0.882 | 757 M | 920 k | 823× |
| 2610.22315_magna | `bh40-S` | 0 | 0.882 | 6.54 G | 491 M | 13.3× |
| 2610.22315_magna | `bh60-T` | 3 | 0.833 | 507 M | 609 k | 832× |
| 2610.22315_magna | `bh60-S` | 0 | 0.833 | 6.39 G | 419 M | 15.2× |
| 2610.22315_magna | `bh80-T` | 3 | 0.788 | 379 M | 454 k | 835× |
| 2610.22315_magna | `bh80-S` | 0 | 0.788 | 6.48 G | 370 M | 17.5× |
| 2610.22315_magna | `bh100-T` | 3 | 0.747 | 290 M | 358 k | 809× |
| 2610.22315_magna | `bh100-S` | 0 | 0.747 | 6.3 G | 317 M | 19.9× |
| 2610.22315_magna | `color3` | 4 | 0.687 | 2.39 G | 43.5 M | 55× |
| 2610.22315_magna | `color5` | 6 | 0.144 | 980 M | 12.8 M | 76.5× |
| 2610.22315_magna | `rp2-d3` | 4 | 0.514 | 1.78 G | 26.3 M | 67.8× |
| 2610.22315_magna | `rp2-d3-S` | 0 | 0.514 | 3.73 G | 41 M | 91× |
| 2610.22315_magna | `rp2-d5` | 9 | 0.0945 | 130 M | 10.1 M | 12.8× |
| 2610.22315_magna | `rp2-d5-S` | 0 | 0.0945 | 2.35 G | 21.5 M | 109× |
| 2610.22315_magna | `dk` | 4 | 0.845 | 1.79 G | 42 M | 42.6× |
| 2610.22315_magna | `wg-T` | 4 | 0.551 | 1.47 G | 14.4 M | 102× |
| 2610.22315_magna | `wg-S` | 0 | 0.551 | 3.49 G | 78.5 M | 44.5× |

</details>

## Testing
- **Gate:** `uv run tsim-dev check` with a plain `uv sync`, no extra, on
the M4 Pro: lint passes, 2757 tests pass, the Metal tests included. CI
runs on Linux, where the Metal tests skip.
- **Generator:** with the Metal dialect, #284's generator writes the
same tapes as the Metal prototype (190 tapes compared), apart from the
intended `draw_one` change.
- **Against the CPU:** `test_metal_sampler.py` compares with the
stabilizer-rank strategy and the CPU executor. It covers postselection,
reference flips, column layouts, booleans against packed rows, deferred
noise, wide rows, width-11 unrolled and looping tapes, and rare
outcomes.
- **Fixes:** a review found eight problems with sharing, interrupting,
forking, pickling, limits and very large calls, and a second review
found three more. A third review led to the SIMD-lane probe, the CPU
fallback on a compile error, hashed noise streams per block, and a
two-sided test of the 48-bit noise path. Each fix has a test, and I
checked that each test fails without its fix, except the fork test,
whose old behaviour is a hang, and the stream hashing, whose collisions
are too rare to construct.

## Not verified
- Other Apple GPUs than the M4 Pro, a real out-of-memory condition, and
Windows (the fork hook is guarded, but nothing ran there).
- `tsim-dev bench --suite full` on the CPU path. The CPU path is
unchanged unless a GPU is used; the quick A/B against `origin/main`
without Metal was at 0.99–1.02×.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant