音频微调需额外装 audio extra 并用 brew 安装 ffmpeg;GGUF 导出要求基座模型未量化,4bit 模型需另找变通方案;合并到 4bit 基座可能把弱 LoRA 增量量化掉,建议用默认 merged_16bit 或保留 adapter。
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.
uv pip install mlx-tune
uv pip install 'mlx-tune[audio]'需要 Apple Silicon Mac(M1/M2/M3/M4/M5),利用统一内存,Mac Studio 最高 512GB;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 使用
容易踩的坑
- 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-tunePurely additive — no existing API changes. New entry points:
FastJEPAModel,FastVideoJEPAModel,LLMJEPATrainer,FastWorldModel(+predict_latents/latent_energy/VJEPA2PretrainedVideoClassifieron 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 DocsNew examples
examples/58_lejepa_pretraining.py— LeJEPA from-scratch + linear probeexamples/59_ijepa_feature_extraction.py— I-JEPA: probes + LoRA fine-tune + save/load/predictexamples/60_vjepa2_video.py— V-JEPA 2: video probe + LoRA fine-tune + save/load/predictexamples/61_llm_jepa_finetuning.py— LLM-JEPA: JEPA objective for LLM fine-tuningexamples/62_lewm_world_model.py— LeWM: train a latent world model + CEM/MPC planningexamples/63_jepa_dense_regression.py— counting / depth / segmentation headsexamples/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
- 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 withmodel.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-tuneNo 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
- 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)
Workload Before (0.4.25) After (0.5.0) Δ GRPO ( Qwen3.5-0.8B-4bit, 8 iters × 4 gen × 128 tok)363.78 s 38.43 s 9.5× DPO per-sample, Qwen3.5-0.8B-MLX-4bit(long-prompt, bs=1)811 ms 539 ms 1.50× ORPO per-sample, Qwen3.5-0.8B-MLX-4bit(long-prompt, bs=1)804 ms 432 ms 1.86× ORPO at ctx=4096, Qwen3-0.6B-bf16(bs=1)OOM on 48 GB 9.23 GB peak (with GC) first version that fits RNN-T value_and_grad (B=2 T=128 U=32 V=64) 491 ms 330 ms 1.49× (bit-exact) Embedding training (16 steps, post-bucketing) eager baseline 1.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.
Workload Model Ctx Batch Speed Peak GB Notes SFT Qwen3-0.6B-bf16 1024 1 ~2.2 it/s 2.6 mlx-lm defaults (16 layers, q/v, no GC) SFT Qwen3-0.6B-bf16 4096 1 ~0.28 it/s 18 full layers + q/v, no GC; matches direct mlx-lm DPO Qwen3.5-0.8B-MLX-4bit 512 1 ~540 ms/sample ~1 shared-prefix fast path DPO Qwen3.5-0.8B-MLX-4bit 512 2 ~810 ms/sample ~1.2 compile + bucket ORPO Qwen3.5-0.8B-MLX-4bit 512 1 ~430 ms/sample ~1 shared-prefix fast path ORPO Qwen3-0.6B-bf16 4096 1 ~0.09 it/s 9.2 GC opt-in required ORPO Qwen3-0.6B-bf16 2048 1 ~0.41 it/s 16.8 no GC GRPO Qwen3-0.6B-bf16 4096 1 (×4 gen × 256 tok) ~0.11 it/s 5.3 reasoning bench GRPO Qwen3.5-0.8B-4bit ~128–512 1 (×4 gen × 128 tok) ~4.8 s/iter 15.9 quantized; Phase 2 backward through 0.8B-4bit Embedding MiniLM-L6-v2-bf16 (22M) 128 32 ~25 ms/step <0.5 InfoNCE contrastive Full reference table and tuning knobs at docs/performance.html.
Full Changelog: https://github.com/arahim3/mlx-tune/commits/v0.5.0
- 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/CPTTrainerAPI as every other model. AFMoE (model_type: "afmoe") is natively supported bymlx_lm.models.afmoe, so no architecture code lives in mlx-tune.# Example Description 54 Trinity-Nano SFT Instruction SFT on mlabonne/FineTome-100k (ShareGPT → ChatML) 55 Trinity-Nano GRPO on GSM8K Two-phase SFT-warmup + GRPO with layered reward; all the production scaling knobs in the docstring 56 Trinity-Nano CPT Continual 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:
- Phase 1 — SFT warmup on GSM8K rationales reshaped into
<reasoning>/<answer>XML (drop the inline<<arithmetic>>and#### Nmarkers). - 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
- Phase 1 — SFT warmup on GSM8K rationales reshaped into
- 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 withKeyError: 'pixel_values'— both for the pre-trainingtranscribe()test and forbatch_transcribe()post-training evaluation.Upgrade
uv pip install -U mlx-tuneNo 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 intransformers >= 5.0. Sincemlx-lm >= 0.31requirestransformers >= 5.0, a freshuv pip install mlx-tunecannot load DeepSeek-OCR out of the box. For DeepSeek-OCR specifically, pintransformers<5.0 mlx-lm<0.31 mlx-vlm<0.4and installaddict einops matplotlibmanually. See README Known Limitation
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
[!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 tomlx-tune. The vision remains the same — bringing the Unsloth experience to Mac users via MLX. If you were usingunsloth-mlx, simply switch topip install mlx-tuneand update your imports fromunsloth_mlxtomlx_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-tuneto 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
FastLanguageModelcode 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.
| Feature | Status | Notes |
|---|---|---|
| SFT Training | ✅ Stable | Native MLX training |
| Model Loading | ✅ Stable | Any HuggingFace model (quantized & non-quantized) |
| Save/Export | ✅ Stable | HF format, GGUF (see limitations) |
| DPO Training | ✅ Stable | Full DPO loss |
| ORPO Training | ✅ Stable | Full ORPO loss |
| GRPO Training | ✅ Stable | Multi-generation + reward |
| KTO Training | ✅ Stable | Binary feedback + KTOConfig |
| SimPO Training | ✅ Stable | No ref model + SimPOConfig |
| Chat Templates | ✅ Stable | 16 models (llama, gemma, qwen, phi, mistral) |
| Response-Only Training | ✅ Stable | train_on_responses_only() |
| Multi-turn Merging | ✅ Stable | to_sharegpt() + conversation_extension |
| Column Mapping | ✅ Stable | apply_column_mapping() auto-rename |
| Dataset Config | ✅ Stable | HFDatasetConfig structured loading |
| Vision Models | ✅ Stable | Full VLM fine-tuning via mlx-vlm (Gemma 4, Qwen3.5, PaliGemma, LLaVA, Pixtral) |
| Gemma 4 Audio | ✅ Stable | E2B/E4B STT/ASR via Conformer audio tower + optional audio LoRA |
| MoE Fine-Tuning | ✅ Stable | Arcee Trinity-Nano (AFMoE), Gemma 4 26B-A4B, Qwen3.5-35B-A3B, Phi-3.5-MoE, Mixtral, DeepSeek, 39+ architectures |
| TTS Fine-Tuning | ✅ Stable | Orpheus, OuteTTS, Spark-TTS, Sesame/CSM, Qwen3-TTS |
| STT Fine-Tuning | ✅ Stable | Whisper, Moonshine, Qwen3-ASR, Canary, Voxtral, Voxtral Realtime (streaming), Parakeet TDT (CTC/RNN-T/TDT losses + auto vocab extension) |
convert() | ✅ Stable | HF → MLX conversion (LLM, TTS, STT) |
| Embedding Fine-Tuning | ✅ Stable | BERT, ModernBERT, Qwen3-Embedding, Harrier (InfoNCE/contrastive) |
| OCR Fine-Tuning | ✅ Stable | DeepSeek-OCR, GLM-OCR, olmOCR, Qwen-VL, Pixtral + CER/WER metrics |
| JEPA / LeJEPA | ✅ Stable | From-scratch ViT SSL (LeJEPA: SIGReg, no EMA/predictor/stop-grad) |
| I-JEPA (pretrained) | ✅ Stable | Meta facebook/ijepa_* → MLX (HF-parity verified); frozen / LoRA / full classification; linear / kNN / attentive probes; warm-start SSL |
| V-JEPA 2 (video) | ✅ Stable | Meta 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 | ✅ Stable | JEPA 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 | ✅ Stable | for_image_regression (object counting) + for_dense_prediction (depth maps, segmentation) — I-JEPA's own headline tasks; frozen / LoRA / full |
| JEPA scale-up | ✅ Stable | Non-224 pretrained I-JEPA (bicubic pos-embed interpolation) + ImageFolderDataset streaming + resumable checkpoints |
| LeWM (world model) | ✅ Stable | LeWorldModel (arXiv 2603.19312): trainable latent world model from pixels (SIGReg, no stop-grad) + CEM/MPC planning; FastWorldModel, plan_cem |
| LFM2 Support | ✅ Stable | Liquid AI LFM2/LFM2.5 (350M-24B, hybrid conv+GQA, Thinking) |
| Continual Pretraining | ✅ Stable | CPTTrainer with decoupled LR, embed_tokens/lm_head, full-weight mode |
push_to_hub() | ✅ Stable | Upload to HuggingFace Hub |
| PyPI Package | ✅ Available | uv 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 usemodel.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
| Method | Trainer | Implementation | Use Case |
|---|---|---|---|
| SFT | SFTTrainer | ✅ Native MLX | Instruction fine-tuning |
| DPO | DPOTrainer | ✅ Native MLX | Preference learning (proper log-prob loss) |
| ORPO | ORPOTrainer | ✅ Native MLX | Combined SFT + odds ratio preference |
| GRPO | GRPOTrainer | ✅ Native MLX | Reasoning with multi-generation (DeepSeek R1 style) |
| KTO | KTOTrainer | ✅ Native MLX | Kahneman-Tversky optimization |
| SimPO | SimPOTrainer | ✅ Native MLX | Simple preference optimization |
| VLM SFT | VLMSFTTrainer | ✅ Native MLX | Vision-Language model fine-tuning |
| Vision GRPO | VLMGRPOTrainer | ✅ Native MLX | Vision-Language GRPO reasoning |
| TTS SFT | TTSSFTTrainer | ✅ Native MLX | Orpheus, OuteTTS, Spark-TTS, Sesame/CSM |
| STT SFT | STTSFTTrainer | ✅ Native MLX | Whisper, Moonshine, Qwen3-ASR, Canary, Voxtral, Voxtral Realtime, Parakeet TDT (CTC/RNN-T/TDT, auto vocab extension) |
| Embedding | EmbeddingSFTTrainer | ✅ Native MLX | BERT, ModernBERT, Qwen3-Embedding, Harrier (InfoNCE) |
| OCR SFT | OCRSFTTrainer | ✅ Native MLX | DeepSeek-OCR, GLM-OCR, Qwen-VL, Pixtral (CER/WER eval) |
| OCR GRPO | OCRGRPOTrainer | ✅ Native MLX | OCR with character-level RL rewards |
| MoE | SFTTrainer | ✅ Native MLX | Arcee Trinity-Nano (AFMoE), Qwen3.5-MoE, Phi-3.5-MoE, Mixtral, DeepSeek (39+ archs) |
| CPT | CPTTrainer | ✅ Native MLX | Continual pretraining with decoupled LR, embed training |
| LFM2 | SFTTrainer | ✅ Native MLX | Liquid 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
| Feature | Unsloth (CUDA) | MLX-Tune |
|---|---|---|
| Platform | NVIDIA GPUs | Apple Silicon |
| Backend | Triton Kernels | MLX Framework |
| Memory | VRAM (limited) | Unified (up to 512GB) |
| API | Original | 100% Compatible |
| Best For | Production training | Local 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:
-
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! -
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 -
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:
- mlx-lm #353 - MLX to GGUF conversion
- mlx-examples #1382 - Quantized to GGUF
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 classfromAutoProcessor.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
- Unsloth - The original, incredible CUDA library
- MLX - Apple's ML framework
- MLX-LM - LLM utilities for MLX
- MLX-VLM - Vision model support
- MLX-Audio - Audio inference (TTS/STT) for MLX
- MLX-Embeddings - Embedding models for MLX