Add MLX backend for Apple silicon#49
Open
ovuruska wants to merge 2 commits into
Open
Conversation
ovuruska
requested review from
abhidas,
erzel,
rajatsen91,
siriuz42,
tamannarayan and
weihaokong
as code owners
July 4, 2026 15:11
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
ovuruska
marked this pull request as draft
July 4, 2026 15:12
ovuruska
marked this pull request as ready for review
July 4, 2026 16:44
Add tabfm/src/mlx/ following the existing backend pattern (model.py + tabfm_v1_0_0.py). The MLX port mirrors the PyTorch module/parameter naming and Linear [out, in] layout, so it loads the PyTorch v1.0.0 safetensors release directly with no weight conversion step. - Faithful forward-path port with the same fp32 upcasts as JAX/PyTorch (RMSNorm, Fourier expansion, PerDimScale softplus, RoPE phases); SDPA at scale=1.0 with checkpoint-loaded RoPE frequencies; weights materialized eagerly at load(). - sklearn wrappers dispatch MLX models through the shared eager execution path (_predict_step_mlx); JAX path untouched. - torch<->mlx parity test asserts < 1e-4 max abs diff in float32 for classification and regression; sklearn fit/predict integration tests. - mlx extra pinned to >=0.31; CI installs it so the tests run on ubuntu-latest. - Also fixes the pytorch extra missing safetensors (load() raised NameError inside PyTorchModelHubMixin on a bare .[pytorch] install) and adds the missing safetensors pin to requirements.txt. Full suite: 76 passed with jax+torch+mlx installed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an MLX backend (
tabfm/src/mlx/,pip install -e .[mlx]) for native Apple-silicon inference, following the existing backend pattern (model.py+tabfm_v1_0_0.py).The port mirrors the PyTorch module/parameter naming and
Linear[out, in]layout, so it loads the PyTorch v1.0.0 safetensors release (google/tabfm-1.0.0-pytorch) directly — no weight conversion step and no separate checkpoint release needed.Also fixes a pre-existing bug found while testing: the
pytorchextra was missingsafetensors, sotabfm_v1_0_0_pytorch.load()failed with aNameErroron a bare.[pytorch]install.What's included
tabfm/src/mlx/model.pyscale=1.0, checkpoint-loaded RoPE freqs)tabfm/src/mlx/tabfm_v1_0_0.pyload()with HF download, process-wide cache, bf16 defaulttabfm/__init__.pytabfm_v1_0_0_mlxexportclassifier_and_regressor.pyHAS_MLXflag,_predict_step_mlx, MLX dispatch in both_batch_forwards (the PyTorch branch is generalized to a shared eager branch; JAX path untouched)pyproject.tomlmlxextra;safetensorsadded to thepytorchextra (bug fix)conftest.pymlx(ortorch, for the parity test) is not installedmlxextra (manylinux cp311 wheels exist) so the new tests run onubuntu-latest[Unreleased]Numerical fidelity
torch<->mlxparity test (tabfm/src/mlx/model_test.py): max abs diff < 1e-4 in float32 for both classification and regression, with zero-initialized tensors randomized so all paths (Fourier buffers, PerDimScale, cls/ind tokens) are exercised.gelu(matchingjax.nn.gelu) is implemented manually since MLX's built-in gelu variants use erf/sigmoid approximations; MLX has noone_hot, so it is built by comparison against the class range.Released-checkpoint compatibility
load_weights).Performance (measured)
Real v1.0.0 classification weights; 200 train / 50 test rows, 8 numeric + 2 categorical features,
n_estimators=8; each backend in a fresh process. "Warm" = secondpredict_probaafter fit.Warm inference: ~1.9× faster than torch-MPS, ~17× faster than torch-CPU. Time-to-first-prediction: 12.8 s vs 66.6 s (torch-MPS incl. load) — safetensors mmap makes the load step cheap (weights are now materialized eagerly at
load()per review feedback). The 0.94-vs-0.92 accuracy delta is one borderline sample out of 50 (bf16 noise), consistent with the 4.3e-3 probability agreement.vs. classic tabular ML baselines (same dataset, default hyperparameters)
Does tuning close the gap? On a harder task (nonlinear target with a categorical interaction, same 200/50 split, 5 dataset seeds), zero-shot TabFM beats even a tuned XGBoost on every seed:
Hyperparameter search moves XGBoost by only +0.004 at 200 training rows — the small-data regime is exactly where in-context learning pays off. On the easier linearly-separable dataset above, both tie at 0.940.
Honest framing: gradient-boosted trees remain far faster on small tabular data. TabFM matches XGBoost's top accuracy here zero-shot — no per-dataset training or tuning (
fit()only prepares encoders/ensemble views). This table is a speed/behavior sanity check; dataset-quality comparisons are the TabArena evals inresults/*.parquet.All numbers benchmarked on an Apple M1 Pro (16 GB).
Test plan
pytest -vv tabfm/src/mlx/model_test.py tabfm/src/classifier_and_regressor_mlx_test.py— 4 passed (Apple M1 Pro, macOS, Python 3.11, mlx 0.31.2, torch 2.12.1)load()+fit/predict/predict_probawith the real classification checkpoint, MLX vs PyTorch cross-check (max prob diff 4.3e-3, identical predictions)🤖 Generated with Claude Code