mlx-tune

ARahim3Apache-2.0更新放缓

在 Apple Silicon Mac 上原生微调大模型的工具,基于 MLX 支持 SFT、DPO、GRPO 以及视觉、语音、OCR 等多模态训练,API 兼容 Unsloth。

Fine-tune LLMs on your Mac with Apple Silicon. SFT, DPO, GRPO, Vision, TTS, STT, Embedding, and OCR fine-tuning — natively on MLX. Unsloth-compatible API.

v0.6.0发布于 2026-06-23 12:24 UTC1,457star102fork同步于 2026-11-07 07:31 UTC

怎么上手

由 README 提炼,执行前请确认
uv pip install mlx-tune
uv pip install 'mlx-tune[audio]'
硬件要求按 README 说明

需要 Apple Silicon Mac(M1/M2/M3/M4/M5),利用统一内存,Mac Studio 最高 512GB;README 未给出具体显存门槛。

支持 Apple Silicon需要 CUDA
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

在 Apple Silicon Mac 上用 MLX 本地微调 LLM、视觉、音频与 OCR 模型,API 兼容 Unsloth

在 Apple Silicon Mac 上原生微调大模型的工具,基于 MLX 支持 SFT、DPO、GRPO 以及视觉、语音、OCR 等多模态训练,API 兼容 Unsloth。

核心能力

  • 封装 MLX 原生训练,支持 SFT、DPO、ORPO、GRPO、KTO、SimPO
  • API 与 Unsloth 对齐,Mac 上写的脚本可直接迁到 CUDA 集群
  • 支持 VLM 微调,可分别控制视觉层与语言层 LoRA
  • 支持 TTS/STT 音频微调,含 Gemma 4 音频塔与音频 LoRA
  • 导出 HF 格式与 GGUF,可给 Ollama/llama.cpp 使用

容易踩的坑

音频微调需额外装 audio extra 并用 brew 安装 ffmpeg;GGUF 导出要求基座模型未量化,4bit 模型需另找变通方案;合并到 4bit 基座可能把弱 LoRA 增量量化掉,建议用默认 merged_16bit 或保留 adapter。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v0.6.0
  1. v0.6.0v0.6.0 — JEPA comes to Apple Silicon最新正式版2026-06-23 · 2026-06-23 12:24 UTC

    v0.6.0 — JEPA comes to Apple Silicon

    A big feature release. mlx-tune now supports the JEPA family — Yann LeCun's Joint-Embedding Predictive Architecture — end to end on a Mac, across vision, language, and world models:

    • Vision SSL — train LeJEPA from scratch, or load Meta's pretrained I-JEPA (images) and V-JEPA 2 (video) and fine-tune them with LoRA.
    • LLM-JEPA — bring the JEPA objective to LLM fine-tuning (the JEPA paper's own LLM follow-up).
    • LeWM — train a latent world model from pixels and plan with it (CEM/MPC).
    • V-JEPA 2 world model surface — Meta's masked-latent predictor is ported too: anticipate the rest of a clip in representation space and score how surprising the actual future was. Plus Meta's fine-tuned SSv2 action classifiers (174 classes) — zero training, just .predict().

    As far as I can tell, this is the first solid train-and-fine-tune JEPA stack on MLX — and the first time LLM-JEPA and LeWorldModel exist on MLX at all.

    Both pretrained vision ports are converted HuggingFace→MLX and numerically verified against the reference PyTorch models (cosine similarity 1.000000), so the features you get out are the real thing.

    Upgrade

    uv pip install -U mlx-tune
    

    Purely additive — no existing API changes. New entry points: FastJEPAModel, FastVideoJEPAModel, LLMJEPATrainer, FastWorldModel (+ predict_latents / latent_energy / VJEPA2PretrainedVideoClassifier on the V-JEPA 2 track).

    What is JEPA (and is it an LLM)?

    JEPA is a predict-in-representation-space paradigm. A model is trained to predict one view's representation from another, in latent space — capturing predictable structure (semantics, dynamics) instead of reconstructing pixels/tokens. The hard problem is representation collapse; each variant fights it differently. Most JEPA work is vision, but the same idea now extends to LLMs (LLM-JEPA) and to world models (LeWM).

    For more details about JEPA in mlx-tune: JEPA Docs

    New examples

    • examples/58_lejepa_pretraining.py — LeJEPA from-scratch + linear probe
    • examples/59_ijepa_feature_extraction.py — I-JEPA: probes + LoRA fine-tune + save/load/predict
    • examples/60_vjepa2_video.py — V-JEPA 2: video probe + LoRA fine-tune + save/load/predict
    • examples/61_llm_jepa_finetuning.py — LLM-JEPA: JEPA objective for LLM fine-tuning
    • examples/62_lewm_world_model.py — LeWM: train a latent world model + CEM/MPC planning
    • examples/63_jepa_dense_regression.py — counting / depth / segmentation heads
    • examples/64_vjepa2_predictor_classifier.py — V-JEPA 2 predictor (anticipation + surprise energy) + Meta's pretrained SSv2 classifier

    Full Changelog: https://github.com/arahim3/mlx-tune/compare/v0.5.1...v0.6.0

  2. v0.5.1v0.5.1 — Fix: `save_pretrained_merged` on a quantized base (issue #15)2026-05-31 · 2026-05-31 09:37 UTC
    查看更新说明

    Patch release.

    If you fine-tuned a 4-bit base (e.g. Llama-3.2-1B-Instruct-4bit) and saved with model.save_pretrained_merged(...), the reloaded merged model could behave like the base model — the fine-tune was lost — even though inference right after training looked correct. Fixed. If you hit this, just re-merge after upgrading; your adapters were fine all along.

    Upgrade

    uv pip install -U mlx-tune
    

    No API changes. The default save_method="merged_16bit" now does the right thing.

    Affected before fix

    Merging a LoRA adapter into a quantized base with save_pretrained_merged — most likely to bite with light fine-tunes. A strong fine-tune (higher LR / more steps) often survived the 4-bit round-trip, which is why it was intermittent.

    Unaffected

    Adapter-only saves (save_pretrained + load_adapter), merging into a non-quantized (bf16/fp16) base, and the VLM / STT / Embedding merge paths (separate code).

    Full Changelog: https://github.com/arahim3/mlx-tune/commits/v0.5.1

  3. v0.5.0v0.5.0 — Performance improvements across every trainer2026-05-19 · 2026-05-19 18:03 UTC
    查看更新说明

    A focused performance release. Most trainers in the project got faster, ORPO at 4096 context fits on a 48 GB Mac for the first time, and a handful of long-standing internal inefficiencies are gone. Everything is under the hood — existing scripts get the speedups with no code changes. A few new optional knobs are introduced for tuning unusual workloads.

    Headline numbers (M4 Pro)

    WorkloadBefore (0.4.25)After (0.5.0)Δ
    GRPO (Qwen3.5-0.8B-4bit, 8 iters × 4 gen × 128 tok)363.78 s38.43 s9.5×
    DPO per-sample, Qwen3.5-0.8B-MLX-4bit (long-prompt, bs=1)811 ms539 ms1.50×
    ORPO per-sample, Qwen3.5-0.8B-MLX-4bit (long-prompt, bs=1)804 ms432 ms1.86×
    ORPO at ctx=4096, Qwen3-0.6B-bf16 (bs=1)OOM on 48 GB9.23 GB peak (with GC)first version that fits
    RNN-T value_and_grad (B=2 T=128 U=32 V=64)491 ms330 ms1.49× (bit-exact)
    Embedding training (16 steps, post-bucketing)eager baseline1.84×compile + bucketing

    What to expect on M4 Pro 48 GB

    Rough indicators from our own benches — not a comparison with other tools, just "what we've measured on this Mac." LoRA rank 8–16 unless noted.

    WorkloadModelCtxBatchSpeedPeak GBNotes
    SFTQwen3-0.6B-bf1610241~2.2 it/s2.6mlx-lm defaults (16 layers, q/v, no GC)
    SFTQwen3-0.6B-bf1640961~0.28 it/s18full layers + q/v, no GC; matches direct mlx-lm
    DPOQwen3.5-0.8B-MLX-4bit5121~540 ms/sample~1shared-prefix fast path
    DPOQwen3.5-0.8B-MLX-4bit5122~810 ms/sample~1.2compile + bucket
    ORPOQwen3.5-0.8B-MLX-4bit5121~430 ms/sample~1shared-prefix fast path
    ORPOQwen3-0.6B-bf1640961~0.09 it/s9.2GC opt-in required
    ORPOQwen3-0.6B-bf1620481~0.41 it/s16.8no GC
    GRPOQwen3-0.6B-bf1640961 (×4 gen × 256 tok)~0.11 it/s5.3reasoning bench
    GRPOQwen3.5-0.8B-4bit~128–5121 (×4 gen × 128 tok)~4.8 s/iter15.9quantized; Phase 2 backward through 0.8B-4bit
    EmbeddingMiniLM-L6-v2-bf16 (22M)12832~25 ms/step<0.5InfoNCE contrastive

    Full reference table and tuning knobs at docs/performance.html.

    Full Changelog: https://github.com/arahim3/mlx-tune/commits/v0.5.0

  4. v0.4.25v0.4.25 — Arcee Trinity-Nano (AFMoE) Fine-Tuning2026-04-21 · 2026-04-21 20:06 UTC
    查看更新说明

    What's New

    Three recipes, one model

    # uv pip install -U mlx-tune
    from mlx_tune import FastLanguageModel
    
    model, tok = FastLanguageModel.from_pretrained(
        "mlx-community/Trinity-Nano-Preview-4bit",
        load_in_4bit=True,
    )
    model = FastLanguageModel.get_peft_model(model, r=8, lora_alpha=16,
        target_modules=["q_proj","k_proj","v_proj","o_proj",
                        "gate_proj","up_proj","down_proj"])
    # Prints: "MoE architecture detected — LoRA will target expert layers"
    # Resolves 14 paths: experts.{gate,up,down}_proj + shared_experts.{gate,up,down}_proj
    # + self_attn.{q,k,v,o,gate}_proj (gated-attention gate_proj picked up automatically)
    

    Same FastLanguageModel / SFTTrainer / GRPOTrainer / CPTTrainer API as every other model. AFMoE (model_type: "afmoe") is natively supported by mlx_lm.models.afmoe, so no architecture code lives in mlx-tune.

    #ExampleDescription
    54Trinity-Nano SFTInstruction SFT on mlabonne/FineTome-100k (ShareGPT → ChatML)
    55Trinity-Nano GRPO on GSM8KTwo-phase SFT-warmup + GRPO with layered reward; all the production scaling knobs in the docstring
    56Trinity-Nano CPTContinual pretraining on Salesforce/wikitext (wikitext-2-raw-v1) with decoupled embedding LR

    GRPO recipe rule

    Starting GRPO cold on a preview/base model with a strict reward function produces zero gradient — every completion gets reward 0, advantage collapses to 0, optimizer no-ops. Example 55 now ships the published two-phase recipe:

    1. Phase 1 — SFT warmup on GSM8K rationales reshaped into <reasoning>/<answer> XML (drop the inline <<arithmetic>> and #### N markers).
    2. Phase 2 — GRPO with a layered reward: 1.0 strict-XML+correct / 0.7 format+correct / 0.5 correct-only / 0.25 any-tag / 0 otherwise.

    Partial credit creates variance even before the model masters the format. Production-scale knobs are documented in the example docstring.

    Full Changelog: https://github.com/arahim3/mlx-tune/commits/v0.4.25

  5. v0.4.24v0.4.24 — VLM/OCR bugfix cycle2026-04-18 · 2026-04-18 13:11 UTC
    查看更新说明

    Patch release, follow-up to v0.4.23.

    After upgrading to v0.4.23, fine-tuning DeepSeek-OCR (or any VLM with a custom processor that carries image data under a non-standard key) would crash at inference time with KeyError: 'pixel_values' — both for the pre-training transcribe() test and for batch_transcribe() post-training evaluation.

    Upgrade

    uv pip install -U mlx-tune
    

    No API changes.

    Known limitation: DeepSeek-OCR + transformers 5.x

    During this investigation, we hit a separate upstream issue: DeepSeek-OCR's model repo imports LlamaFlashAttention2, which was removed in transformers >= 5.0. Since mlx-lm >= 0.31 requires transformers >= 5.0, a fresh uv pip install mlx-tune cannot load DeepSeek-OCR out of the box. For DeepSeek-OCR specifically, pin transformers<5.0 mlx-lm<0.31 mlx-vlm<0.4 and install addict einops matplotlib manually. See README Known Limitation

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    [!NOTE] Name Change: This project was originally called unsloth-mlx. Since it's not an official Unsloth project and to avoid any confusion, it has been renamed to mlx-tune. The vision remains the same — bringing the Unsloth experience to Mac users via MLX. If you were using unsloth-mlx, simply switch to pip install mlx-tune and update your imports from unsloth_mlx to mlx_tune.

    [!NOTE] Why I Built This (A Personal Note)

    I rely on Unsloth for my daily fine-tuning on cloud GPUs—it's the gold standard for me. But recently, I started working on a MacBook M4 and hit a friction point: I wanted to prototype locally on my Mac, then scale up to the cloud without rewriting my entire training script.

    Since Unsloth relies on Triton (which Macs don't have, yet), I couldn't use it locally. I built mlx-tune to solve this specific "Context Switch" problem. It wraps Apple's native MLX framework in an Unsloth-compatible API.

    The goal isn't to replace Unsloth or claim superior performance. The goal is code portability: allowing you to write FastLanguageModel code once on your Mac, test it, and then push that exact same script to a CUDA cluster. It solves a workflow problem, not just a hardware one.

    This is an "unofficial" project built by a fan, for fans who happen to use Macs. It's helping me personally, and if it helps others like me, then I'll have my satisfaction.

    Why MLX-Tune?

    Bringing the Unsloth experience to Mac users via Apple's MLX framework.

    • 🚀 Fine-tune LLMs, VLMs, TTS, STT & Embeddings locally on your Mac (M1/M2/M3/M4/M5)
    • 💾 Leverage unified memory (up to 512GB on Mac Studio)
    • 🔄 Unsloth-compatible API - your existing training scripts just work!
    • 📦 Export anywhere - HuggingFace format, GGUF for Ollama/llama.cpp
    • 🎙️ Audio fine-tuning - 5 TTS models (Orpheus, OuteTTS, Spark, Sesame, Qwen3-TTS) + 7 STT models (Whisper, Moonshine, Qwen3-ASR, NVIDIA Canary, Voxtral, Voxtral Realtime, NVIDIA Parakeet TDT)
    # Unsloth (CUDA)                        # MLX-Tune (Apple Silicon)
    from unsloth import FastLanguageModel   from mlx_tune import FastLanguageModel
    from trl import SFTTrainer              from mlx_tune import SFTTrainer
    
    # Rest of your code stays exactly the same!
    

    What This Is (and Isn't)

    This is NOT a replacement for Unsloth or an attempt to compete with it. Unsloth is incredible - it's the gold standard for efficient LLM fine-tuning on CUDA.

    This IS a bridge for Mac users who want to:

    • 🧪 Prototype locally - Experiment with fine-tuning before committing to cloud GPU costs
    • 📚 Learn & iterate - Develop your training pipeline with fast local feedback loops
    • 🔄 Then scale up - Move to cloud NVIDIA GPUs + original Unsloth for production training
    Local Mac (MLX-Tune)       →     Cloud GPU (Unsloth)
       Prototype & experiment          Full-scale training
       Small datasets                  Large datasets
       Quick iterations                Production runs
    

    Project Status

    🧠 v0.6.0 — JEPA comes to Apple Silicon. Yann LeCun's Joint-Embedding Predictive Architecture, the whole family:

    • LeJEPA — pretrain a Vision Transformer from scratch with the heuristics-free SIGReg objective (no EMA teacher, no predictor, no stop-gradient).
    • I-JEPA — load Meta's pretrained image encoder (facebook/ijepa_*) and fine-tune with frozen / LoRA / full classification.
    • V-JEPA 2 — load Meta's pretrained video world model (facebook/vjepa2-*): video classification, clip features, the masked-latent predictor (predict_latents + latent_energy — anticipate the rest of a clip, score how surprising the actual future was), and Meta's fine-tuned SSv2 action classifiers (174 classes, zero training).
    • LLM-JEPA — bring the JEPA objective to LLM fine-tuning (arXiv 2509.14252): augment next-token prediction with a JEPA term that aligns two "views" of the same item (e.g. a description and its code). First-on-MLX; the artifact is a normal LoRA model.

    New entry points: FastJEPAModel, FastVideoJEPAModel, LLMJEPATrainer. See the JEPA docs.

    FeatureStatusNotes
    SFT Training✅ StableNative MLX training
    Model Loading✅ StableAny HuggingFace model (quantized & non-quantized)
    Save/Export✅ StableHF format, GGUF (see limitations)
    DPO Training✅ StableFull DPO loss
    ORPO Training✅ StableFull ORPO loss
    GRPO Training✅ StableMulti-generation + reward
    KTO Training✅ StableBinary feedback + KTOConfig
    SimPO Training✅ StableNo ref model + SimPOConfig
    Chat Templates✅ Stable16 models (llama, gemma, qwen, phi, mistral)
    Response-Only Training✅ Stabletrain_on_responses_only()
    Multi-turn Merging✅ Stableto_sharegpt() + conversation_extension
    Column Mapping✅ Stableapply_column_mapping() auto-rename
    Dataset Config✅ StableHFDatasetConfig structured loading
    Vision Models✅ StableFull VLM fine-tuning via mlx-vlm (Gemma 4, Qwen3.5, PaliGemma, LLaVA, Pixtral)
    Gemma 4 Audio✅ StableE2B/E4B STT/ASR via Conformer audio tower + optional audio LoRA
    MoE Fine-Tuning✅ StableArcee Trinity-Nano (AFMoE), Gemma 4 26B-A4B, Qwen3.5-35B-A3B, Phi-3.5-MoE, Mixtral, DeepSeek, 39+ architectures
    TTS Fine-Tuning✅ StableOrpheus, OuteTTS, Spark-TTS, Sesame/CSM, Qwen3-TTS
    STT Fine-Tuning✅ StableWhisper, Moonshine, Qwen3-ASR, Canary, Voxtral, Voxtral Realtime (streaming), Parakeet TDT (CTC/RNN-T/TDT losses + auto vocab extension)
    convert()✅ StableHF → MLX conversion (LLM, TTS, STT)
    Embedding Fine-Tuning✅ StableBERT, ModernBERT, Qwen3-Embedding, Harrier (InfoNCE/contrastive)
    OCR Fine-Tuning✅ StableDeepSeek-OCR, GLM-OCR, olmOCR, Qwen-VL, Pixtral + CER/WER metrics
    JEPA / LeJEPA✅ StableFrom-scratch ViT SSL (LeJEPA: SIGReg, no EMA/predictor/stop-grad)
    I-JEPA (pretrained)✅ StableMeta facebook/ijepa_* → MLX (HF-parity verified); frozen / LoRA / full classification; linear / kNN / attentive probes; warm-start SSL
    V-JEPA 2 (video)✅ StableMeta facebook/vjepa2-* video world model → MLX (Conv3d + 3D RoPE, HF-parity verified); video classification + probes; predictor ported (predict_latents/latent_energy anticipation); pretrained SSv2 classifiers load directly
    LLM-JEPA✅ StableJEPA objective for LLM fine-tuning (arXiv 2509.14252): NTP + view-alignment (cosine/l2/mse/infonce), [PRED] slots; LLMJEPATrainer, artifact is a normal LoRA model
    JEPA dense / regression✅ Stablefor_image_regression (object counting) + for_dense_prediction (depth maps, segmentation) — I-JEPA's own headline tasks; frozen / LoRA / full
    JEPA scale-up✅ StableNon-224 pretrained I-JEPA (bicubic pos-embed interpolation) + ImageFolderDataset streaming + resumable checkpoints
    LeWM (world model)✅ StableLeWorldModel (arXiv 2603.19312): trainable latent world model from pixels (SIGReg, no stop-grad) + CEM/MPC planning; FastWorldModel, plan_cem
    LFM2 Support✅ StableLiquid AI LFM2/LFM2.5 (350M-24B, hybrid conv+GQA, Thinking)
    Continual Pretraining✅ StableCPTTrainer with decoupled LR, embed_tokens/lm_head, full-weight mode
    push_to_hub()✅ StableUpload to HuggingFace Hub
    PyPI Package✅ Availableuv pip install mlx-tune

    Installation

    # Using uv (recommended - faster and more reliable)
    uv pip install mlx-tune
    
    # With audio support (TTS/STT fine-tuning)
    uv pip install 'mlx-tune[audio]'
    brew install ffmpeg  # system dependency for audio codecs
    
    # Or using pip
    pip install mlx-tune
    
    # From source (for development)
    git clone https://github.com/ARahim3/mlx-tune.git
    cd mlx-tune
    uv pip install -e .
    

    Quick Start

    from mlx_tune import FastLanguageModel, SFTTrainer, SFTConfig
    from datasets import load_dataset
    
    # Load any HuggingFace model (1B model for quick start)
    model, tokenizer = FastLanguageModel.from_pretrained(
        model_name="mlx-community/Llama-3.2-1B-Instruct-4bit",
        max_seq_length=2048,
        load_in_4bit=True,
    )
    
    # Add LoRA adapters
    model = FastLanguageModel.get_peft_model(
        model,
        r=16,
        target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
        lora_alpha=16,
    )
    
    # Load a dataset (or create your own)
    dataset = load_dataset("yahma/alpaca-cleaned", split="train[:100]")
    
    # Train with SFTTrainer (same API as TRL!)
    trainer = SFTTrainer(
        model=model,
        train_dataset=dataset,
        tokenizer=tokenizer,
        args=SFTConfig(
            output_dir="outputs",
            per_device_train_batch_size=2,
            learning_rate=2e-4,
            max_steps=50,
        ),
    )
    trainer.train()
    
    # Save (same API as Unsloth!)
    model.save_pretrained("lora_model")  # Adapters only
    model.save_pretrained_merged("merged", tokenizer)  # Full model (16-bit)
    model.save_pretrained_gguf("model", tokenizer)  # GGUF (see note below)
    

    [!NOTE] Merging into a 4-bit base: the default save_method="merged_16bit" dequantizes the base before fusing, so the fine-tune is preserved exactly. save_method="merged_4bit" keeps the base quantized and re-quantizes the fused weights — smaller on disk, but a weak LoRA delta (low LR / few steps) can be rounded away. Prefer the 16-bit default, or keep the adapter and use model.load_adapter(...) at inference time.

    [!NOTE] GGUF Export: Works with non-quantized base models. If using a 4-bit model (like above), see Known Limitations for workarounds.

    Chat Templates & Response-Only Training

    from mlx_tune import get_chat_template, train_on_responses_only
    
    # Apply chat template (supports llama-3, gemma, qwen, phi, mistral, etc.)
    tokenizer = get_chat_template(tokenizer, chat_template="llama-3")
    
    # Or auto-detect from model name
    tokenizer = get_chat_template(tokenizer, chat_template="auto")
    
    # Train only on responses (not prompts) - more efficient!
    trainer = train_on_responses_only(
        trainer,
        instruction_part="<|start_header_id|>user<|end_header_id|>\n\n",
        response_part="<|start_header_id|>assistant<|end_header_id|>\n\n",
    )
    

    Vision Model Fine-Tuning (NEW!)

    Fine-tune vision-language models like Gemma 4, Qwen3.5 on image+text tasks:

    from mlx_tune import FastVisionModel, UnslothVisionDataCollator, VLMSFTTrainer
    from mlx_tune.vlm import VLMSFTConfig
    
    # Load a vision model
    model, processor = FastVisionModel.from_pretrained(
        "mlx-community/Qwen3.5-0.8B-bf16",
    )
    
    # Add LoRA (same params as Unsloth!)
    model = FastVisionModel.get_peft_model(
        model,
        finetune_vision_layers=True,
        finetune_language_layers=True,
        r=16, lora_alpha=16,
    )
    
    # Train on image-text data
    FastVisionModel.for_training(model)
    trainer = VLMSFTTrainer(
        model=model,
        tokenizer=processor,
        data_collator=UnslothVisionDataCollator(model, processor),
        train_dataset=dataset,
        args=VLMSFTConfig(max_steps=30, learning_rate=2e-4),
    )
    trainer.train()
    

    See examples/38_gemma4_vision_finetuning.py for Gemma 4 vision fine-tuning, examples/39_gemma4_text_to_sql.py for text-only fine-tuning through the VLM path, examples/10_qwen35_vision_finetuning.py for Qwen3.5, or examples/26_vision_grpo_training.py for Vision GRPO reasoning.

    Gemma 4 Audio Fine-Tuning

    Fine-tune Gemma 4 E2B/E4B for speech-to-text and audio understanding. The 12-layer Conformer audio tower processes 16kHz audio — no separate STT model needed:

    from mlx_tune import FastVisionModel, UnslothVisionDataCollator, VLMSFTTrainer
    from mlx_tune.vlm import VLMSFTConfig
    
    model, processor = FastVisionModel.from_pretrained("mlx-community/gemma-4-e4b-it-4bit")
    model = FastVisionModel.get_peft_model(model,
        finetune_vision_layers=False, finetune_language_layers=True,
        finetune_audio_layers=False,  # Set True for domain-specific acoustic adaptation
        r=16, lora_alpha=16)
    
    # Dataset format: {"type": "audio", "audio": "/path/to/file.wav"}
    dataset = [{"messages": [
        {"role": "user", "content": [
            {"type": "audio", "audio": "audio.wav"},
            {"type": "text", "text": "Transcribe this audio."},
        ]},
        {"role": "assistant", "content": [{"type": "text", "text": "Hello world."}]},
    ]}]
    
    # Inference with audio
    response = model.generate(audio="audio.wav", prompt="Transcribe this audio.")
    

    See examples/47_gemma4_audio_asr_finetuning.py for ASR fine-tuning or examples/48_gemma4_audio_understanding.py for audio understanding with audio tower LoRA.

    TTS Fine-Tuning

    Fine-tune text-to-speech models on Apple Silicon. Supports Orpheus-3B, OuteTTS-1B, Spark-TTS (0.5B), Sesame/CSM-1B, and Qwen3-TTS:

    from mlx_tune import FastTTSModel, TTSSFTTrainer, TTSSFTConfig, TTSDataCollator
    from datasets import load_dataset, Audio
    
    # Auto-detects model type, codec, and token format
    model, tokenizer = FastTTSModel.from_pretrained("mlx-community/orpheus-3b-0.1-ft-bf16")
    # Also works with:
    #   "mlx-community/Llama-OuteTTS-1.0-1B-8bit"   (DAC codec, 24kHz)
    #   "mlx-community/Spark-TTS-0.5B-bf16"          (BiCodec, 16kHz)
    model = FastTTSModel.get_peft_model(model, r=16, lora_alpha=16)
    
    dataset = load_dataset("MrDragonFox/Elise", split="train[:100]")
    dataset = dataset.cast_column("audio", Audio(sampling_rate=24000))
    
    trainer = TTSSFTTrainer(
        model=model, tokenizer=tokenizer,
        data_collator=TTSDataCollator(model, tokenizer),
        train_dataset=dataset,
        args=TTSSFTConfig(output_dir="./tts_output", max_steps=60),
    )
    trainer.train()
    

    See examples: Orpheus, OuteTTS, Spark-TTS, Qwen3-TTS.

    STT Fine-Tuning

    Fine-tune speech-to-text models. Supports Whisper (all sizes), Distil-Whisper, and Moonshine:

    from mlx_tune import FastSTTModel, STTSFTTrainer, STTSFTConfig, STTDataCollator
    
    # Auto-detects model type and preprocessor
    model, processor = FastSTTModel.from_pretrained("mlx-community/whisper-tiny-asr-fp16")
    # Also works with:
    #   "mlx-community/distil-whisper-large-v3"   (Whisper architecture)
    #   "UsefulSensors/moonshine-tiny"             (raw conv frontend)
    model = FastSTTModel.get_peft_model(model, r=8, finetune_encoder=True, finetune_decoder=True)
    
    trainer = STTSFTTrainer(
        model=model, processor=processor,
        data_collator=STTDataCollator(model, processor, language="en", task="transcribe"),
        train_dataset=dataset,
        args=STTSFTConfig(output_dir="./stt_output", max_steps=60),
    )
    trainer.train()
    

    See examples: Whisper, Moonshine, Qwen3-ASR, Canary, Voxtral, Voxtral Realtime (streaming), Parakeet TDT English, Parakeet Welsh (new language), Parakeet Bengali (auto vocab extension), Parakeet Arabic (BPE extension).

    Embedding Fine-Tuning

    Fine-tune sentence embedding models for semantic search using contrastive learning (InfoNCE loss). Supports BERT, ModernBERT, Qwen3-Embedding, Harrier, and more:

    from mlx_tune import FastEmbeddingModel, EmbeddingSFTTrainer, EmbeddingSFTConfig, EmbeddingDataCollator
    
    # Load embedding model (BERT or Qwen3-Embedding)
    model, tokenizer = FastEmbeddingModel.from_pretrained(
        "mlx-community/all-MiniLM-L6-v2-bf16",  # or Qwen3-Embedding-0.6B-4bit-DWQ
        pooling_strategy="mean",                  # "mean", "cls", or "last_token"
    )
    model = FastEmbeddingModel.get_peft_model(model, r=16, lora_alpha=16)
    
    # Train with anchor-positive pairs (in-batch negatives via InfoNCE)
    trainer = EmbeddingSFTTrainer(
        model=model, tokenizer=tokenizer,
        data_collator=EmbeddingDataCollator(model, tokenizer),
        train_dataset=[{"anchor": "query text", "positive": "relevant passage"}, ...],
        args=EmbeddingSFTConfig(
            loss_type="infonce", temperature=0.05,
            per_device_train_batch_size=32, max_steps=50,
        ),
    )
    trainer.train()
    
    # Encode & compare
    embeddings = model.encode(["Hello world", "Hi there"])
    similarity = (embeddings[0] * embeddings[1]).sum().item()
    

    See examples: BERT, Qwen3-Embedding, Harrier-0.6B, Harrier-270M.

    OCR Fine-Tuning

    Fine-tune dedicated OCR models or general VLMs for document understanding, handwriting recognition, LaTeX OCR, multilingual receipts, and more. Built-in CER/WER evaluation metrics:

    from mlx_tune import FastOCRModel, OCRSFTTrainer, OCRSFTConfig, compute_ocr_metrics
    
    # Load a dedicated OCR model (or any VLM like Qwen3.5)
    model, processor = FastOCRModel.from_pretrained(
        "mlx-community/DeepSeek-OCR-8bit",  # 0.9B dedicated OCR model
    )
    model = FastOCRModel.get_peft_model(model, r=16, lora_alpha=16)
    # Vision layers frozen by default (OCR models have pre-optimized encoders)
    
    # Train on OCR data
    trainer = OCRSFTTrainer(
        model=model, processor=processor,
        train_dataset=ocr_dataset,
        args=OCRSFTConfig(max_steps=100, learning_rate=5e-5),
    )
    trainer.train()
    
    # Transcribe & evaluate
    text = model.transcribe(image)
    metrics = model.evaluate(test_images, ground_truths)  # → {cer, wer, exact_match}
    

    Supported OCR models: DeepSeek-OCR, DeepSeek-OCR-2, GLM-OCR, DOTS-OCR, olmOCR-2, LightOnOCR, Qwen2.5-VL, Qwen3.5, Pixtral, and any VLM supported by mlx-vlm.

    See examples: Document OCR, VLM→OCR, Handwriting, OCR GRPO, Multilingual.

    Continual Pretraining (CPT)

    Adapt any model to new domains or languages by training on raw text. Supports LoRA CPT (with optional embedding training) and full-weight CPT:

    from mlx_tune import FastLanguageModel, CPTTrainer, CPTConfig
    
    # Load a BASE model (not instruction-tuned)
    model, tokenizer = FastLanguageModel.from_pretrained(
        "mlx-community/SmolLM2-360M-Instruct", max_seq_length=2048,
    )
    model = FastLanguageModel.get_peft_model(model, r=16, target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj",
    ])
    
    # CPT: raw text, loss on ALL tokens, decoupled embedding LR
    trainer = CPTTrainer(
        model=model, tokenizer=tokenizer,
        train_dataset=[{"text": "Domain-specific document..."}, ...],
        args=CPTConfig(
            learning_rate=5e-5,
            embedding_learning_rate=5e-6,  # 10x smaller for embeddings
            include_embeddings=True,       # auto-adds embed_tokens + lm_head
            max_steps=1000,
        ),
    )
    trainer.train()
    

    See examples: Language Adaptation, Domain Knowledge, Code Capabilities, LFM2 + CPT.

    LFM2 (Liquid AI) Fine-Tuning

    Fine-tune Liquid Foundation Models with their hybrid gated-conv + GQA architecture:

    from mlx_tune import FastLanguageModel, SFTTrainer, SFTConfig
    
    model, tokenizer = FastLanguageModel.from_pretrained(
        "mlx-community/LFM2-350M-4bit", max_seq_length=2048,
    )
    # LFM2-specific target modules (auto-resolved)
    model = FastLanguageModel.get_peft_model(model, r=16, target_modules=[
        "q_proj", "k_proj", "v_proj", "out_proj",  # Attention
        "in_proj", "w1", "w2", "w3",                # Gated conv MLP
    ])
    

    Supported: LFM2 (350M-2.6B dense), LFM2.5 (350M-1.2B), LFM2.5-Thinking, LFM2 MoE (8B-A1B, 24B-A2B).

    See examples: LFM2 SFT, LFM2.5-Thinking.

    MoE Fine-Tuning

    Fine-tune Mixture of Experts models — 39+ architectures supported automatically. MLX-Tune detects MoE layers and applies per-expert LoRA via LoRASwitchLinear:

    from mlx_tune import FastLanguageModel, SFTTrainer, SFTConfig
    
    # Load any MoE model — same API as dense models!
    model, tokenizer = FastLanguageModel.from_pretrained(
        model_name="mlx-community/Qwen3.5-35B-A3B-4bit",  # 35B total, 3B active
        max_seq_length=2048,
        load_in_4bit=True,
    )
    
    # Same target_modules — MoE paths resolved automatically
    model = FastLanguageModel.get_peft_model(
        model, r=8,
        target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
                        "gate_proj", "up_proj", "down_proj"],
    )
    # Prints: "MoE architecture detected — LoRA will target expert layers (SwitchLinear)"
    

    Supported MoE models: Arcee Trinity-Nano (AFMoE, 6B/1B active, 128 experts + 1 shared), Qwen3.5-35B-A3B, Qwen3-30B-A3B, Phi-3.5-MoE, Mixtral, DeepSeek-V2/V3, GLM-MoE, and all other MoE architectures in mlx-lm.

    See examples: Qwen3.5 MoE, Phi-3.5 MoE, Trinity-Nano SFT, Trinity-Nano GRPO, Trinity-Nano CPT.

    Post-Training Workflow

    All model types (LLM, VLM, TTS, STT) support the full post-training workflow:

    # Save LoRA adapters
    model.save_pretrained("./adapters")
    
    # Merge LoRA into base model
    model.save_pretrained_merged("./merged")
    
    # Convert HF model to MLX format
    FastLanguageModel.convert("model-name", mlx_path="./mlx_model")
    
    # Push to HuggingFace Hub
    model.push_to_hub("username/my-model")
    

    Supported Training Methods

    MethodTrainerImplementationUse Case
    SFTSFTTrainer✅ Native MLXInstruction fine-tuning
    DPODPOTrainer✅ Native MLXPreference learning (proper log-prob loss)
    ORPOORPOTrainer✅ Native MLXCombined SFT + odds ratio preference
    GRPOGRPOTrainer✅ Native MLXReasoning with multi-generation (DeepSeek R1 style)
    KTOKTOTrainer✅ Native MLXKahneman-Tversky optimization
    SimPOSimPOTrainer✅ Native MLXSimple preference optimization
    VLM SFTVLMSFTTrainer✅ Native MLXVision-Language model fine-tuning
    Vision GRPOVLMGRPOTrainer✅ Native MLXVision-Language GRPO reasoning
    TTS SFTTTSSFTTrainer✅ Native MLXOrpheus, OuteTTS, Spark-TTS, Sesame/CSM
    STT SFTSTTSFTTrainer✅ Native MLXWhisper, Moonshine, Qwen3-ASR, Canary, Voxtral, Voxtral Realtime, Parakeet TDT (CTC/RNN-T/TDT, auto vocab extension)
    EmbeddingEmbeddingSFTTrainer✅ Native MLXBERT, ModernBERT, Qwen3-Embedding, Harrier (InfoNCE)
    OCR SFTOCRSFTTrainer✅ Native MLXDeepSeek-OCR, GLM-OCR, Qwen-VL, Pixtral (CER/WER eval)
    OCR GRPOOCRGRPOTrainer✅ Native MLXOCR with character-level RL rewards
    MoESFTTrainer✅ Native MLXArcee Trinity-Nano (AFMoE), Qwen3.5-MoE, Phi-3.5-MoE, Mixtral, DeepSeek (39+ archs)
    CPTCPTTrainer✅ Native MLXContinual pretraining with decoupled LR, embed training
    LFM2SFTTrainer✅ Native MLXLiquid AI LFM2/LFM2.5 (hybrid conv+GQA, Thinking)

    Examples

    Check examples/ for working code:

    • Basic model loading and inference (01–07)
    • Complete SFT fine-tuning pipeline (08)
    • RL training overview (09)
    • Vision model fine-tuning — Qwen3.5 (10-11)
    • RL E2E training — DPO (21), GRPO (22), ORPO (23), KTO (24), SimPO (25), Vision GRPO (26)
    • TTS fine-tuning — Orpheus-3B (12), OuteTTS (14), Spark-TTS (15), Qwen3-TTS (20)
    • STT fine-tuning — Whisper (13), Moonshine (16), Qwen3-ASR (17), Canary (18), Voxtral (19), Voxtral Realtime streaming (49), Parakeet TDT English (50), Parakeet Welsh new-language (51), Parakeet Bengali auto vocab extension (52), Parakeet Arabic BPE extension (53)
    • Embedding fine-tuning — BERT/MiniLM (27), Qwen3-Embedding (28), Harrier-0.6B (31), Harrier-270M (32)
    • OCR fine-tuning — Document OCR (33), VLM→OCR (34), Handwriting (35), OCR GRPO (36), Multilingual (37)
    • MoE fine-tuning — Qwen3.5-35B-A3B (29), Phi-3.5-MoE (30), Arcee Trinity-Nano AFMoE: SFT (54), GRPO reasoning (55), CPT (56)
    • LFM2 fine-tuning — LFM2 SFT (41), LFM2.5-Thinking (42)
    • Continual Pretraining — Language (43), Domain (44), Code (45), LFM2+CPT (46)

    Requirements

    • Hardware: Apple Silicon Mac (M1/M2/M3/M4/M5)
    • OS: macOS 13.0+
    • Memory: 8GB+ unified RAM (16GB+ recommended)
    • Python: 3.9+

    Comparison with Unsloth

    FeatureUnsloth (CUDA)MLX-Tune
    PlatformNVIDIA GPUsApple Silicon
    BackendTriton KernelsMLX Framework
    MemoryVRAM (limited)Unified (up to 512GB)
    APIOriginal100% Compatible
    Best ForProduction trainingLocal dev, large models

    Known Limitations

    GGUF Export from Quantized Models

    The Issue: GGUF export (save_pretrained_gguf) doesn't work directly with quantized (4-bit) base models. This is a known limitation in mlx-lm, not an mlx-tune bug.

    What Works:

    • ✅ Training with quantized models (QLoRA) - works perfectly
    • ✅ Saving adapters (save_pretrained) - works
    • ✅ Saving merged model (save_pretrained_merged) - works
    • ✅ Inference with trained model - works
    • ❌ GGUF export from quantized base model - mlx-lm limitation

    Workarounds:

    1. Use a non-quantized base model (recommended for GGUF export):

      # Use fp16 model instead of 4-bit
      model, tokenizer = FastLanguageModel.from_pretrained(
          model_name="mlx-community/Llama-3.2-1B-Instruct",  # NOT -4bit
          max_seq_length=2048,
          load_in_4bit=False,  # Train in fp16
      )
      # Train normally, then export
      model.save_pretrained_gguf("model", tokenizer)  # Works!
      
    2. Dequantize during export (results in large fp16 file):

      model.save_pretrained_gguf("model", tokenizer, dequantize=True)
      # Then re-quantize with llama.cpp:
      # ./llama-quantize model.gguf model-q4_k_m.gguf Q4_K_M
      
    3. Skip GGUF, use MLX format: If you only need the model for MLX/Python inference, just use save_pretrained_merged() - no GGUF needed.

    Related Issues:

    DeepSeek-OCR requires transformers<5.0

    The Issue: DeepSeek-OCR's model repo (mlx-community/DeepSeek-OCR-*) ships remote code that imports LlamaFlashAttention2 from transformers.models.llama.modeling_llama. That symbol was removed in transformers 5.0. Recent mlx-tune installs pull mlx-lm>=0.31, which requires transformers>=5.0, so a fresh pip install mlx-tune cannot load DeepSeek-OCR out of the box.

    Additional missing deps: DeepSeek-OCR's remote code also imports addict, einops, and matplotlib — none of these are declared by mlx-tune, mlx-vlm, or the model repo. You need to install them manually.

    Working environment (verified):

    uv pip install 'transformers>=4.45,<5.0' 'mlx-lm<0.31' 'mlx-vlm<0.4' addict einops matplotlib
    uv pip install mlx-tune --no-deps     # skip dep upgrade
    

    Symptom if you hit it:

    • mlx-vlm raises Unrecognized processing class from AutoProcessor.from_pretrained (the real ImportError is swallowed by mlx-vlm's patch wrapper)
    • Debug by calling DeepseekOCRProcessor.from_pretrained(model_path, trust_remote_code=True) directly to see the underlying error

    DeepSeek-OCR-2 (mlx-community/DeepSeek-OCR-2-*) needs mlx-vlm>=0.4 which needs transformers>=5.0 → currently not loadable anywhere. Tracking this upstream.

    Contributing

    Contributions welcome! Areas that need help:

    • Custom MLX kernels for even faster training
    • More test coverage (especially E2E and edge cases)
    • Testing on different M-series chips (M1, M2, M3, M4, M5)
    • Batched audio training (currently batch_size=1)
    • Batched RL training (currently single-sample)

    License

    Apache 2.0 - See LICENSE file.

    Acknowledgments