Could the brain's slowest calculation run on the Mac's graphics chip, and how much faster?
Yes. One walker move fell from 1.88 s to 0.04 s on the graphics chip, 47 times faster; learning stayed on the main processor.
In the log: the GPU port of the propagation (2026-09-18 10:13; operator: "yes do the GPU port")
recordedDate 2026-09-18 10:13, as written in the logunclassified0 predictions · 0 result paragraphsEXPERIMENTS.md lines 11710–11750
What E184 did and how it came out, drawn from this record and the files it names (book/assets/diagrams/exp/E184.svg).
Results
No result paragraph for this entry was found in the log.
The full record
EXPERIMENTS.md · lines 11710–11750
E184 — the GPU port of the propagation (2026-09-18 10:13; operator: "yes do the GPU port")
What was ported. The one loop the search spends its time in — h = tanh(W h + drive)
with Kenyon rectification over 25.1 M synapses — existed three times (score_batch,
head_scores, PopulationReadout._states). It is now one function,
forager/brain/propagate.py:propagate, with the backend chosen by FORAGER_DEVICE:
unset/cpu = scipy float64 (the reference, unchanged); torch = torch sparse CSR on CPU,
float64; mps/cuda = torch sparse COO on the accelerator, float32. The built operator
sits in model._operator, the slot every weight update already clears, so the eight
invalidation sites are inherited. Learning is untouched: forward (single sample, the
pre cache the three-factor rules read) stays numpy float64 — lessons are exact; only
batch scoring moves. MPS has no sparse-CSR kernel (NotImplementedError); COO works.
Measured, real head (whole brain, broadcast, steps 4, 24 walkers = one walker move):
device per score_batch(24) max|h − scipy| max|score − scipy|
cpu/scipy 1.88 s — —
torch (f64) 1.35 s 1.7e-16 1.6e-19 bit-identical
mps (f32) 0.04 s 7.6e-07 2.7e-10 47× faster
Raw sparse product: scipy 3.60 s / torch-cpu 1.91 s / mps 0.37 s per four products.
Tests: tests/test_propagate_device.py (5) — cpu path equals the pre-port loop exactly,
torch-cpu within 1e-12, float32 device within 1e-4 (measured 8e-7), call sites proven to
use the helper, weight updates invalidate the device operator. 59 brain tests pass.
What it does and does not change. A 200 × 4 × 2 search spent ~2 h in scoring; on MPS
that is ~3 min, and the run's remaining time is the verifier's icet screen and the learning
forward passes (CPU). The float32 path is a different number by 3e-10 in score — far
below any threshold the search applies (p ≥ 0.832 on a −40 meV drive) but not zero, so
device is recorded per run and a scipy-vs-mps replication is the first thing to run on
it: E184b = E171b seed 0 on FORAGER_BRAIN_DEVICE=mps, same seed, same protocol.
Prediction: AUC_Q within 1.0 of the scipy seed-0 value (≈ 33) and finds within ±2 of
81; if either differs by more, a float32 tie-flip in the walker's greedy comparison is
compounding, and the search's device must then stay fixed per experiment family. Queued
behind E173 (one search at a time; the CPU parts of an MPS search still count).
JAX (operator asked). Not for this: JAX has no usable Metal backend for sparse ops
(jax-metal is experimental and jax.experimental.sparse is CUDA/TPU), so on the Mac it
would give nothing; torch covers M4 today and CUDA on Colab with the same code. JAX earns its
place only if the substrate moves to the chaobrain LIF reproduction of Shiu 2024 (JAX) or the
runs go to Colab TPU — both are open decisions, not this port.
Related entries
E184b — first launch failed at 10:17 — my name collision