Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions .context/mps_pathways.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,29 @@ count / block size / n_models to find the crossover, rather than concluding from
32 channels.
`benchmarks/benchmark_gpu.py` already sweeps devices; add a dimension sweep.

## Pathway C: MLX port (longer-term, higher ceiling)
## Pathway C: MLX port -- v1 MVP LANDED (#76)

MLX consistently beats PyTorch MPS on the same hardware (2-3x for LLM inference)
and is Apple-native with a real compiler / lazy graph ([WWDC25](https://developer.apple.com/videos/play/wwdc2025/315/),
[MLX](https://github.com/ml-explore/mlx)); PyTorch MPS remains eager, largely
unfused, and sometimes falls back to CPU ([State of PyTorch HW 2025](https://tunguz.github.io/PyTorch_Hardware_2025/)).
An MLX backend could both cut dispatch overhead and fuse the per-block work.

Cost: a full backend rewrite, still float32-only on GPU (Pathway A, #75, already
provides that), and a new dependency. High effort. Best seen as a v2 acceleration
option, not a near-term step.
**Status (#76):** `AMICAMLXNG` (`pyAMICA/mlx_impl/core.py`) is a v1 MVP -- single-model,
generalized-Gaussian, natural gradient. It is a **hybrid**: the E/M-step hot path runs on
the GPU in float32 (with the Phase A `ufp/y` guard), while all `mlx.core.linalg` is CPU-only
in MLX 0.32, so `inv(A)`/`slogdet(W)` run on the CPU stream (hoisted to once per iteration --
measured ~42 us/iter vs a ~13 ms GPU E-pass, so not the bottleneck; `mx.eval` placement is).
`lgamma`/`digamma` (absent in MLX) are computed host-side via SciPy on the small `rho` array.
It matches the PyTorch float32 backend's converged LL to ~2e-6 and the NumPy reference stats to
rtol ~1e-4. MLX is an optional dependency (Apple Silicon only), so CI skips these tests. Newton,
the other PDF families, sharing, multi-model, and save/load are fast-follows. Whether it beats
CPU/MPS is Pathway B's question.

Cost (estimated before landing, now borne by the #76 MVP): a backend rewrite,
still float32-only on GPU (Pathway A, #75, provides that), and a new optional
dependency. The MVP is in-tree (see Status above); Newton, the other families,
sharing and multi-model remain fast-follows.

## Pathway D: software FP64 emulation -- DEAD END

Expand Down
8 changes: 6 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ pyAMICA/
├── amica.py # Main scikit-learn-style AMICA interface (wraps AMICATorchNG)
├── __init__.py # Exposes AMICA (PyTorch), AMICA_NumPy (legacy), numpy_impl, torch_impl
├── torch_impl/ # PyTorch backend
│ ├── core.py # Natural-gradient EM port (AMICATorchNG); Fortran-parity, sole backend
│ ├── core.py # Natural-gradient EM port (AMICATorchNG); Fortran-parity, primary backend
│ └── utils.py # Preprocessing (sphering, PCA), device selection
├── mlx_impl/ # Optional MLX backend (Apple GPU; AMICAMLXNG, issue #76)
│ └── core.py # float32 GPU E/M-step + CPU-stream linalg (v1 MVP: single-model GG, NG)
├── numpy_impl/ # Legacy NumPy reference (topic-named modules, issue #34)
│ ├── core.py # AMICA_NumPy; newton.py, pdf.py, data.py, load.py, viz.py, utils.py, cli.py
│ └── ...
Expand All @@ -25,7 +27,9 @@ validate_implementations.py # Runs both implementations, Hungarian component ma
Module names are topic-based (`core`/`newton`/`pdf`/`data`/... under `numpy_impl/`,
`core`/`utils` under `torch_impl/`); the old `pyAMICA.py`/`amica_*.py`/`amica_torch_ng.py`
prefixes were dropped in issue #34. The public import surface is stable:
`from pyAMICA import AMICA, AMICA_NumPy, AMICATorchNG`.
`from pyAMICA import AMICA, AMICA_NumPy, AMICATorchNG`. The optional MLX backend is
imported separately (`from pyAMICA.mlx_impl import AMICAMLXNG`) so `import pyAMICA` never
requires MLX; install it with `uv pip install mlx` or the `mlx` extra (Apple Silicon only).

## Environment Setup
Canonical environment is **UV** (per global standards). The PyTorch stack is declared in
Expand Down
16 changes: 16 additions & 0 deletions pyAMICA/mlx_impl/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""MLX backend for pyAMICA (Apple-Silicon GPU).

Mirrors ``torch_impl`` but targets Apple's MLX array framework. MLX is an
*optional* dependency (Apple Silicon only); importing this subpackage requires
``mlx`` to be installed, so the top-level ``pyAMICA`` package never imports it
eagerly. Install with ``uv pip install mlx`` (or the ``mlx`` extra).

The backend (:class:`AMICAMLXNG`) runs the natural-gradient EM E/M-step on the
Apple GPU in float32 (Apple GPUs have no FP64), with the small per-iteration
linear algebra on MLX's CPU stream (issue #76, epic #74 Phase C). It is a v1
MVP: single-model, generalized-Gaussian (``pdftype=0``), natural gradient.
"""

from .core import AMICAMLXNG

__all__ = ["AMICAMLXNG"]
Loading
Loading