Repository navigation
chore(dev): pin tsim-bench with call sizes that fit a GPU - #292
Conversation
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>
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 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".
| device = side.device or args.device | ||
| if device not in calibrated: |
There was a problem hiding this comment.
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 👍 / 👎.
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>
Problem
tsim-dev bench --device gpureported GPU throughput about 3.7x too low. The tsim-bench worker capped one sampling call at2^28 / outputsshots, as if every shot came back as unpacked booleans.tsim-dev benchasks for bit-packed rows, though, and a postselected call only returns the rows it keeps. Onend2end_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
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_roundscalibrates once per device, so sides on different devices each time a call of their own size. There is a unit test for this.The new pin also brings in tsim-bench
9bc81ad(qv_q10: one observable per measured qubit). That changes whatqv_q10measures, 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:eb4a6d11(old pin)19804f26(new pin)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):uv run tsim-dev checkpasses (lint, 2618 tests), andpython -m tsim_bench checkreports 0 failed.Merge order
rafaelha/tsim-bench#2 must merge first.
REVpins its branch commit19804f26, which_exportcan fetch because it lives on a pushed branch. If that PR is squash-merged, re-pinREVhere to the merged commit on tsim-benchmainbefore merging.🤖 Generated with Claude Code