MLX-LM 模型微调技术手册

3 阅读8分钟

本文适用于在 Apple Silicon Mac 上使用 MLX-LM 进行 LoRA/QLoRA 微调。示例使用当前项目;迁移到其他模型时替换模型 ID、数据目录和输出目录即可。

1. 约定与目录

通用占位符:

BASE_MODEL   Hugging Face 模型 ID 或本地模型目录
DATA_DIR     包含 train.jsonl、valid.jsonl、test.jsonl 的目录
ADAPTER_DIR  LoRA adapter 输出目录
FUSED_DIR    合并模型输出目录

推荐结构:

project/
├── data/{train,valid,test}.jsonl
├── adapters/<run-name>/
├── fused/<model-name>/
├── scripts/
└── docs/

不要提交基础模型、完整数据集、adapter、检查点和实验输出。

2. 创建与检查环境

cd /Users/qing/work/mlx
python3.14 -m venv .venv-mlx-train
source .venv-mlx-train/bin/activate
python -m pip install --upgrade pip
python -m pip install mlx-lm
python --version
python -m pip show mlx mlx-lm

当前机器使用以下前缀绕过单机任务不需要的 MPI 动态库加载:

MLX_MPI_LIBNAME=/nonexistent/libmpi.dylib <command>

3. 准备数据

聊天数据每行必须是一个独立 JSON 对象:

{"messages":[{"role":"user","content":"你是谁?"},{"role":"assistant","content":"我是小八。"}]}

验证 JSONL、角色顺序和样本数:

python - <<'PY'
import json
from pathlib import Path

root = Path("qwen3-finetune/data")
for path in (root / "train.jsonl", root / "valid.jsonl", root / "test.jsonl"):
    rows = [json.loads(line) for line in path.read_text().splitlines() if line.strip()]
    for index, row in enumerate(rows, 1):
        assert "messages" in row, (path, index)
        assert row["messages"][-1]["role"] == "assistant", (path, index)
    print(f"{path}: {len(rows)} samples")
PY

训练、验证和测试集不要包含完全重复的问题。测试集应包含训练时未出现的表达方式。

4. 启动 LoRA 微调

当前项目命令:

MLX_MPI_LIBNAME=/nonexistent/libmpi.dylib mlx_lm.lora \
  --model mlx-community/Qwen3-4B-4bit \
  --train \
  --data ./qwen3-finetune/data \
  --adapter-path ./qwen3-finetune/adapters/xiaoba \
  --batch-size 1 \
  --num-layers 8 \
  --max-seq-length 512 \
  --iters 100 \
  --learning-rate 1e-5 \
  --save-every 20 \
  --grad-checkpoint \
  --mask-prompt

关键参数:

参数作用
--num-layers从模型末尾开始训练的层数,越大越耗内存
--iters优化步数,不等同于样本数
--learning-rate学习率;过高会破坏通用能力,过低可能学不到
--mask-prompt只对最终 assistant 回复计算损失
--grad-checkpoint用更多计算换取更低内存占用
--save-every定期保存阶段检查点

先以小步数验证数据和命令,再增加训练量。不要仅凭训练 loss 选择模型,应比较验证集和未见问题。

5. 从检查点继续训练

MLX_MPI_LIBNAME=/nonexistent/libmpi.dylib mlx_lm.lora \
  --model mlx-community/Qwen3-4B-4bit \
  --train \
  --data ./qwen3-finetune/data \
  --adapter-path ./qwen3-finetune/adapters/xiaoba-resumed \
  --resume-adapter-file ./qwen3-finetune/adapters/xiaoba/0000100_adapters.safetensors \
  --batch-size 1 \
  --num-layers 8 \
  --max-seq-length 512 \
  --iters 100 \
  --learning-rate 1e-5 \
  --save-every 20 \
  --grad-checkpoint \
  --mask-prompt

使用新输出目录保存续训结果,避免覆盖已验证的 adapter。

6. 评估和推理

计算测试集指标:

MLX_MPI_LIBNAME=/nonexistent/libmpi.dylib mlx_lm.lora \
  --model mlx-community/Qwen3-4B-4bit \
  --data ./qwen3-finetune/data \
  --adapter-path ./qwen3-finetune/adapters/xiaoba \
  --test \
  --test-batches -1

直接验证 adapter:

MLX_MPI_LIBNAME=/nonexistent/libmpi.dylib mlx_lm.generate \
  --model mlx-community/Qwen3-4B-4bit \
  --adapter-path ./qwen3-finetune/adapters/xiaoba \
  --prompt "你是谁?" \
  --chat-template-config '{"enable_thinking":false}' \
  --max-tokens 64 \
  --temp 0

交互式聊天:

MLX_MPI_LIBNAME=/nonexistent/libmpi.dylib mlx_lm.chat \
  --model mlx-community/Qwen3-4B-4bit \
  --adapter-path ./qwen3-finetune/adapters/xiaoba

Qwen3 默认可能输出 <think>。测试身份或短回答时关闭 thinking,否则输出可能在最终答案前达到 max_tokens。

7. 启动 MLX-LM API

MLX_MPI_LIBNAME=/nonexistent/libmpi.dylib mlx_lm.server \
  --model mlx-community/Qwen3-4B-4bit \
  --adapter-path ./qwen3-finetune/adapters/xiaoba \
  --host 127.0.0.1 \
  --port 8000 \
  --chat-template-args '{"enable_thinking":false}'
curl http://127.0.0.1:8000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "default_model",
    "messages": [{"role":"user","content":"你是谁?"}],
    "temperature": 0,
    "max_tokens": 64
  }'

该服务适合开发和内网验证。不要在没有反向代理、鉴权和限流的情况下直接暴露到公网。

8. 合并 adapter

合并并保持量化格式:

MLX_MPI_LIBNAME=/nonexistent/libmpi.dylib mlx_lm.fuse \
  --model mlx-community/Qwen3-4B-4bit \
  --adapter-path ./qwen3-finetune/adapters/xiaoba \
  --save-path ./qwen3-finetune/fused/xiaoba-4bit

LoRA 增量可能在重新量化时丢失。当前“小八”实验已经出现 4-bit 合并模型退化为基础模型身份,因此必须验证,不能仅检查文件是否生成。

合并为未量化模型以更完整地保留 LoRA:

MLX_MPI_LIBNAME=/nonexistent/libmpi.dylib mlx_lm.fuse \
  --model mlx-community/Qwen3-4B-4bit \
  --adapter-path ./qwen3-finetune/adapters/xiaoba \
  --save-path ./qwen3-finetune/fused/xiaoba-bf16 \
  --dequantize
MLX_MPI_LIBNAME=/nonexistent/libmpi.dylib mlx_lm.generate \
  --model ./qwen3-finetune/fused/xiaoba-bf16 \
  --prompt "你是谁?" \
  --chat-template-config '{"enable_thinking":false}' \
  --max-tokens 64 \
  --temp 0

BF16 模型体积和运行内存通常约为相同 4-bit 模型的四倍。磁盘或内存有限时,保留 4-bit 基础模型并动态加载 adapter。

9. 发布到 Hugging Face

登录并创建私有仓库:

hf auth login
hf repos create YOUR_NAME/qwen3-4b-xiaoba-mlx-lora --repo-type model --private

只发布 adapter:

hf upload YOUR_NAME/qwen3-4b-xiaoba-mlx-lora \
  ./qwen3-finetune/adapters/xiaoba/adapters.safetensors adapters.safetensors
hf upload YOUR_NAME/qwen3-4b-xiaoba-mlx-lora \
  ./qwen3-finetune/adapters/xiaoba/adapter_config.json adapter_config.json

发布完整 BF16 合并模型:

hf repos create YOUR_NAME/Qwen3-4B-Xiaoba-BF16 --repo-type model --private
HF_XET_HIGH_PERFORMANCE=1 hf upload \
  YOUR_NAME/Qwen3-4B-Xiaoba-BF16 \
  ./qwen3-finetune/fused/xiaoba-bf16 .

模型卡至少记录基础模型、许可证、数据用途、MLX-LM 版本、训练参数、硬件、评估结果和使用命令。不要上传私有数据、密钥或未经许可的权重。

10. 排错顺序

  1. 用 mlx_lm.generate --adapter-path 验证原始 adapter。
  2. 用 mlx_lm.generate --model <fused-dir> 验证合并模型。
  3. 最后验证部署框架,避免把训练问题与服务问题混在一起。
  4. 返回基础模型身份时,检查是否使用了正确 adapter、API model 名称,以及量化融合是否损失效果。
  5. 内存不足时依次减小 --max-seq-length、--num-layers 和 batch size。

11. MLX-LM 命令集速查

以下命令基于本项目当前使用的 mlx-lm 0.32.0。升级后先查看帮助,确认参数是否变化:

source .venv-mlx-train/bin/activate
python -m pip show mlx-lm
mlx_lm --version
mlx_lm --help
mlx_lm.generate --help
mlx_lm.lora --help

mlx_lm.<command> 也可写成 python -m mlx_lm <command>。如果当前机器仍有 MPI 动态库加载问题,在命令前添加 MLX_MPI_LIBNAME=/nonexistent/libmpi.dylib。

11.1 命令目录

命令用途是否直接支持 adapter
mlx_lm.loraLoRA/QLoRA 训练、验证及测试是,--adapter-path
mlx_lm.generate单轮文本生成和快速回归测试是,--adapter-path
mlx_lm.chat终端多轮对话是,--adapter-path
mlx_lm.server启动 OpenAI 兼容 HTTP API是,--adapter-path
mlx_lm.fuse将 LoRA adapter 合并进基础模型读取 adapter 后生成完整模型
mlx_lm.convert将 Hugging Face 模型转换为 MLX,可同时量化不适用
mlx_lm.evaluate使用 lm-evaluation-harness 运行任务评测否,先合并模型
mlx_lm.perplexity在数据集上计算困惑度否,先合并模型
mlx_lm.benchmark测量 prompt 与生成吞吐量否,先合并模型
mlx_lm.cache_prompt保存可复用的 prompt/KV cache是,--adapter-path
mlx_lm.manage扫描或删除 Hugging Face 本地模型缓存不适用
mlx_lm.upload上传本地 MLX 模型目录到 Hugging Face不适用
mlx_lm.share在 MLX 分布式节点间共享模型或目录不适用
mlx_lm.awq使用 AWQ 生成量化模型不适用
mlx_lm.gptq使用 GPTQ 生成量化模型不适用
mlx_lm.dwq使用蒸馏权重量化生成或改进量化模型不适用
mlx_lm.dynamic_quant按层敏感度生成混合位宽模型不适用

11.2 常用推理、服务和性能命令

验证 adapter 时必须同时传入训练所用的基础模型:

mlx_lm.generate \
  --model mlx-community/Qwen3-4B-4bit \
  --adapter-path ./qwen3-finetune/adapters/xiaoba \
  --prompt "你是谁?" \
  --chat-template-config '{"enable_thinking":false}' \
  --max-tokens 64 \
  --temp 0

mlx_lm.chat \
  --model mlx-community/Qwen3-4B-4bit \
  --adapter-path ./qwen3-finetune/adapters/xiaoba \
  --max-tokens 256 \
  --temp 0

API 服务命令见第 7 节。吞吐测试应针对已经合并的部署产物,保证测量对象与发布对象一致:

mlx_lm.benchmark \
  --model ./qwen3-finetune/fused/xiaoba-bf16 \
  --prompt-tokens 512 \
  --generation-tokens 128 \
  --batch-size 1 \
  --num-trials 5

对固定的长 system prompt 建立缓存并在生成时复用:

mlx_lm.cache_prompt \
  --model mlx-community/Qwen3-4B-4bit \
  --adapter-path ./qwen3-finetune/adapters/xiaoba \
  --prompt "你是小八,请始终简洁回答。" \
  --prompt-cache-file ./qwen3-finetune/outputs/xiaoba-prompt-cache.safetensors

mlx_lm.generate \
  --model mlx-community/Qwen3-4B-4bit \
  --adapter-path ./qwen3-finetune/adapters/xiaoba \
  --prompt-cache-file ./qwen3-finetune/outputs/xiaoba-prompt-cache.safetensors \
  --prompt "你是谁?"

缓存必须与模型、adapter 和提示前缀匹配;任一项变化后重新生成。

11.3 转换、合并和量化

转换 Hugging Face 模型并保存为 BF16 MLX 模型:

mlx_lm.convert \
  --hf-path Qwen/Qwen3-4B \
  --mlx-path ./qwen3-finetune/models/Qwen3-4B-BF16 \
  --dtype bfloat16

转换并进行常规 4-bit 量化:

mlx_lm.convert \
  --hf-path Qwen/Qwen3-4B \
  --mlx-path ./qwen3-finetune/models/Qwen3-4B-4bit \
  --quantize \
  --q-bits 4 \
  --q-group-size 64

adapter 合并命令见第 8 节。需要其他量化算法时先阅读帮助,再写入新的输出目录:

mlx_lm.awq --model ./qwen3-finetune/fused/xiaoba-bf16 --mlx-path ./qwen3-finetune/models/xiaoba-awq-4bit
mlx_lm.gptq --model ./qwen3-finetune/fused/xiaoba-bf16 --mlx-path ./qwen3-finetune/models/xiaoba-gptq-4bit
mlx_lm.dynamic_quant --model ./qwen3-finetune/fused/xiaoba-bf16 --mlx-path ./qwen3-finetune/models/xiaoba-dynamic
mlx_lm.dwq --model ./qwen3-finetune/fused/xiaoba-bf16 --mlx-path ./qwen3-finetune/models/xiaoba-dwq-4bit

AWQ、GPTQ、DWQ 和动态量化需要校准数据、更多时间和内存。量化完成后必须使用相同测试问题和评测集与 BF16 合并模型对比,不能只比较文件大小。

11.4 评估和模型管理

通用任务评测依赖 lm-evaluation-harness,且 mlx_lm.evaluate 不接收 --adapter-path,因此应传入合并模型:

mlx_lm.evaluate \
  --model ./qwen3-finetune/fused/xiaoba-bf16 \
  --tasks mmlu_pro \
  --output-dir ./qwen3-finetune/outputs/evaluation \
  --batch-size 1 \
  --limit 20 \
  --chat-template-args '{"enable_thinking":false}'

mlx_lm.perplexity \
  --model ./qwen3-finetune/fused/xiaoba-bf16 \
  --data-path allenai/tulu-3-sft-mixture \
  --batch-size 1 \
  --sequence-length 512 \
  --num-samples 32

身份微调还应保留项目自己的精确匹配或语义评测;通用 benchmark 不能代替业务验收集。

扫描模型缓存和发布完整 MLX 模型:

mlx_lm.manage --scan
mlx_lm.manage --scan --pattern Qwen3
mlx_lm.upload \
  --path ./qwen3-finetune/fused/xiaoba-bf16 \
  --upload-repo YOUR_NAME/Qwen3-4B-Xiaoba-BF16

mlx_lm.manage --delete --pattern <pattern> 会删除匹配的本地缓存,执行前先用相同 pattern 运行 --scan 核对目标。mlx_lm.share 仅用于已经配置好的 MLX 多机分布式环境,单机微调无需使用。

参考:MLX-LM、MLX-LM LoRA。