Skip to content

Phase B: cross-platform dimension-sweep benchmark - #80

Merged
neuromechanist merged 2 commits into
feature/issue-74-epic-apple-gpufrom
feature/issue-77-phaseB-gpu-benchmark
Jul 8, 2026
Merged

Phase B: cross-platform dimension-sweep benchmark#80
neuromechanist merged 2 commits into
feature/issue-74-epic-apple-gpufrom
feature/issue-77-phaseB-gpu-benchmark

Conversation

@neuromechanist

Copy link
Copy Markdown
Member

Summary

Epic #74 Phase B: the benchmark that answers what Phases A and C were building toward -- does an Apple/NVIDIA GPU actually beat the CPU for AMICA, and where? Measures both results (converged LL) and performance (ms/it) for every backend (numpy / torch-cpu / torch-mps / torch-cuda / mlx) on real 70-channel EEG (OpenNeuro ds002718 sub-002), sweeping channel count and n_models with component sharing on/off.

benchmarks/benchmark_dimsweep.py auto-detects the host's backends, records warmed min-of-repeats timing + LL at matched settings, and emits JSON so a Mac run and a CUDA-host run merge via --report. MLX (single-model MVP) auto-excludes from multi-model/sharing configs. Data is fetched from OpenNeuro (public), not committed (README_dimsweep.md; benchmarks/data/ + result JSONs gitignored).

Headline results (single-model, ms/iteration)

ch mlx cuda-f32 torch-cpu-f32 torch-mps-f32 numpy
32 21 36 143 162 624
70 25 36 173 255 1350
  • MLX is the Apple-GPU win: ~15-25 ms/it, flat across 16-70 ch, ~7x over torch-CPU and faster than an RTX 4090 (CUDA ~36 ms) at EEG scale.
  • PyTorch-MPS never wins (162-255 ms/it, at or worse than CPU) -- the Apple-GPU acceleration is MLX, not device="mps".
  • Results agree across cpu/mps/cuda/mlx and f32/f64 to ~3 digits on real data (LL ~ -3.216 at 70ch).
  • Multi-model has no GPU path yet (MLX MVP is single-model; MPS loses) -- multi-model MLX is the top fast-follow.

Full tables + caveats: .context/issue-77/benchmark_findings.md.

Test plan

This is a benchmark harness (no unit tests / not run in CI). Validated by running it: full single-model sweep on Apple Silicon (cpu/mps/mlx) + RTX 4090 (cuda), and multi-model (m2, m2+share) on Apple Silicon. --report merges the per-platform JSONs into ms/it + LL tables. ruff clean.

Caveats / pending

  • MLX vs CUDA is cross-machine (Apple M-series vs an RTX 4090 host), not a same-box comparison -- read as "best Apple-GPU path vs a strong NVIDIA GPU".
  • Multi-model CUDA is pending (the hallu SSH session dropped mid-run); single-model CUDA is complete. Re-run --n-models 2 [--share] --backends torch-cuda-* on the CUDA host to fill it in.

Closes #77
Part of epic #74

Epic #74 Phase B: measure both results and performance for every AMICA backend
across CPU/MPS/CUDA/MLX on real 70-channel EEG (OpenNeuro ds002718 sub-002),
sweeping channel count and n_models with component sharing on/off.

benchmarks/benchmark_dimsweep.py auto-detects the host's backends
(numpy/torch-cpu/torch-mps/torch-cuda/mlx), records ms/iteration (warmed,
min-of-repeats) and converged LL at matched settings, and emits JSON so a Mac
run and a CUDA host run merge via --report. MLX (single-model MVP) auto-excludes
from multi-model/sharing configs. Data is fetched from OpenNeuro, not committed
(README_dimsweep.md; benchmarks/data + result JSONs gitignored).

Findings (.context/issue-77/benchmark_findings.md):
- MLX is the Apple-GPU win: ~15-25 ms/it, flat across 16-70 ch, ~7x over
  torch-CPU and faster than an RTX 4090 (CUDA ~36 ms) at EEG scale.
- PyTorch-MPS never wins (162-255 ms/it, at or worse than CPU) -- use MLX, not
  device="mps", on Apple hardware.
- Results agree across cpu/mps/cuda/mlx and f32/f64 to ~3 digits on real data.
- Multi-model has no GPU path yet (MLX MVP is single-model; MPS loses), so
  multi-model MLX is the top fast-follow.

Docs updated: mps_pathways.md Pathway B (DONE), AGENTS.md perf note.
The numpy adapter didn't pass block_size, so AMICA_NumPy used its default
(block_size=128, do_opt_block=True) and ran an in-fit wall-clock block-size
auto-tune search inside the timed region -- unmatched vs torch/mlx (which pin
block_size=512) and timing pollution that ~doubled numpy's ms/it. Pass
block_size=512, do_opt_block=False. Corrected numpy: 142-622 ms/it (was
310-1350), so numpy is ~10-25x slower than MLX, not 50-70x. Results (LL)
unchanged (block_size does not affect the accumulated sufficient stats). Findings
table updated; the qualitative conclusions (MLX ~7x win, MPS never wins) are
unaffected.
@neuromechanist

Copy link
Copy Markdown
Member Author

Review (Sonnet code-reviewer, worktree-isolated)

One valid finding (confidence 84), fixed in 8f26b39; everything else confirmed correct.

Fixed: the numpy adapter didn't pin block_size, so AMICA_NumPy used its default (128) and ran an in-fit do_opt_block block-size auto-tune search inside the timed region -- unmatched vs torch/mlx (which pin 512) and timing pollution that ~doubled numpy's ms/it. Now passes block_size=512, do_opt_block=False. Corrected numpy: 142-622 ms/it (was 310-1350) -> numpy is ~10-25x slower than MLX, not 50-70x. Results (LL) are unchanged (block_size doesn't affect the accumulated sufficient stats), and the headline conclusions (MLX ~7x win; PyTorch-MPS never wins) are unaffected. Findings table updated. (The PR body's numpy column above is pre-fix; see .context/issue-77/benchmark_findings.md for the corrected numbers.)

Confirmed correct by the reviewer (cross-referenced against the actual backends): the numpy LL normalization (m.ll[-1]/(ch*samples) matches the torch/mlx per-sample-per-channel scale), the CUDA synchronize() placement, MLX timing (fit's per-iter mx.eval prevents lazy carryover), the MLX single-model availability gate, the share_iter/share_int handling (sharing fires at iters 5/13 within the 20-iter budget), the --report formatting, and compliance (no emojis/attribution, real data not committed, timing benchmark on channel subsets of real EEG).

@neuromechanist
neuromechanist merged commit b923b8a into feature/issue-74-epic-apple-gpu Jul 8, 2026
5 checks passed
@neuromechanist
neuromechanist deleted the feature/issue-77-phaseB-gpu-benchmark branch July 8, 2026 17:14
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