Skip to content

RFC: native MLX training backend for needle finetune on Apple Silicon #128

Description

@nandoolle

Summary

I have a working MLX port of the training-path forward pass plus LoRA finetuning (needle finetune --backend mlx), and would like to know if there is interest in upstreaming it before I invest in rebasing it onto the current architecture.

Motivation

The current macOS GPU path depends on jax-metal, which pins jax==0.4.38, needs ENABLE_PJRT_COMPATIBILITY, and forces flash/remat/scan off. A native MLX backend removes the plugin and the pin, and runs on the current jax for everything else. During my usage of ee221ce, which my fork was based on, the metal extra also produced NaN losses in the first steps but trained cleanly after changing to MLX.

What was built

563 lines, 6 files, fork branch feat/mlx-finetune-backend.

  • needle/model/architecture_mlx.py:
    • MLX forward of exactly what finetuning exercises (SimpleAttentionNetwork, quant=False). Flat param dict mirroring the flax tree
    • mx.checkpoint per MHC layer, mirroring nn.remat. dtype=bfloat16 follows the flax semantics (dense/attention in bf16
    • norms, MHC mixing, sinkhorn, logits in f32). Confidence/contrastive heads, MTP and KV-window decode are out of scope.
  • needle/model/finetune_mlx.py:
    • same data format, LoRA placement (five stacked attention kernels), AdamW + warmup/cosine, global-norm clip 1.0, masked CE, val holdout. Step compiled with mx.compile. Writes the same adapter .pkl, so needle build --lora works unchanged.
  • CLI:
    • --backend {jax,mlx} and --dtype {float32,bfloat16} on finetune (jax remains the default).
  • pyproject.toml:
    • train-mlx extra (mlx>=0.29, numpy, sentencepiece).
  • tests/test_mlx_parity.py:
    • f32 forward parity vs the JAX model on the published checkpoint, argmax-identical, max-abs logit diff ~3e-3 (skips without jax+mlx+checkpoint).

Known gap

The port targets the needle2 architecture at ee221ce, and since that main has added sliding_window, global_layers, ladder_*, qkv_conv_taps, qk_head_dim, safetensors and depth rungs, none of which the MLX forward implements yet. Rebasing is a rewrite of the forward, hence this RFC first.

Questions

  1. Is a second forward implementation acceptable maintenance-wise, given the parity test pins it to the JAX reference?
  2. Would you prefer this as a replacement for the metal extra or alongside it?
  3. Is the architecture on main stable enough to port against now?

Happy to open a PR against main if there is interest.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions