本文适用于在 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. 排错顺序
- 用
mlx_lm.generate --adapter-path验证原始 adapter。 - 用
mlx_lm.generate --model <fused-dir>验证合并模型。 - 最后验证部署框架,避免把训练问题与服务问题混在一起。
- 返回基础模型身份时,检查是否使用了正确 adapter、API
model名称,以及量化融合是否损失效果。 - 内存不足时依次减小
--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.lora | LoRA/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。