Fix MLX save and GGUF export parity issues#697
Conversation
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive regression tests and fixes for MLX save and GGUF export parity, including VLM processor repairs, tied embedding materialization, and VLM weight sanitization. The review feedback suggests improving robustness by safely checking if thinker_config is a dictionary before accessing its attributes to prevent potential AttributeErrors, and expanding the saved weights check to support single model.safetensors files in addition to sharded indexes.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR improves MLX model save/export parity (especially for VLMs) to better match the CUDA path, including GGUF export behavior and preserved sidecar metadata, and adds regression tests for these scenarios.
Changes:
- Enhance config extraction/saving for MLX-LM vs MLX-VLM, including dataclass handling and backend-aware
save_config. - Add export-time fixes for GGUF/VLM parity (tensor rewrites, tied
lm_headmaterialization, and llama.cpp path anchoring). - Repair degraded MLX-VLM processors by rebuilding image processor + processor class from sidecar configs; add a dedicated regression test suite.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
unsloth_zoo/mlx/utils.py |
Adds backend-aware config saving, tied-embedding materialization, VLM GGUF rewrite pipeline, sidecar copying, and GGUF export adjustments. |
unsloth_zoo/mlx/loader.py |
Repairs degraded VLM processors from sidecar configs and filters supported kwargs for GGUF save/push bindings. |
unsloth_zoo/llama_cpp.py |
Ensures patched converter scripts are written in the correct directory for package layouts. |
tests/test_mlx_save_export_regressions.py |
Adds regression tests covering the new MLX save/export parity behaviors without heavy model downloads. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Prefer HF vision aliases when inverting mlx-vlm sanitizers, while keeping same-name tensor rewrites as a fallback. This preserves Gemma3 patch embedding layout fixes and avoids stopping early on Qwen-family MLX vision_tower names.
Use loaded VLM model instances when replaying mlx-vlm sanitizers for GGUF tensor rewrites, while keeping the config-derived class pipeline as a fallback. This covers models whose top-level sanitizer delegates through submodules, such as GLM-OCR.
444b88b to
0336fc3
Compare
This reverts commit d8cdf89. Also removes the related regression coverage and later helper hardening now that bug-4 is out of scope.
|
Triple-confirmed this PR end-to-end: 1. Local Linux, four Python versions47 tests pass on each of 3.10 / 3.11 / 3.12 / 3.13: the 23 regressions from 2. Cross-OS shim path
3. Real Apple Silicon mlx wheels plus Studio bootDrove Pre-PR vs post-PR diffCopied this PR's test file onto Coverage per fix
Notes for the maintainer
LGTM. |
This PR fixes several issues around MLX merged saves and GGUF export parity.
Bug list:
VLM merged/full saves were missing VLM config fidelity
Root cause: MLX merged saves used the text-only
mlx_lm.utils.save_configpath for VLM checkpoints, so VLM-specific config structure, normalization, and fields were not preserved inconfig.json.Commit: 615aff8
QLoRA merged 16-bit saves were not fully dequantized
Root cause: The merged 16-bit path treated LoRA fusion with
fuse(dequantize=True)as sufficient dequantization, but quantized non-LoRA modules could remain packed.Commit: 55fa173
VLM GGUF mmproj output was metadata-only / missing real projector tensors
Root cause: The temporary GGUF staging directory contained mlx-vlm sanitized tensor names/layouts, while llama.cpp MMPROJ conversion expects HF/llama.cpp projector names/layouts; in-place rewriting also risked corrupting file-backed tensors.
Commit: 6fcaa19
MLX GGUF export options like
first_conversionwere dropped by the bound save methodRoot cause: The monkey-patched MLX
save_pretrained_ggufwrapper accepted CUDA-compatible**kwargsbut discarded them when calling the utility implementation, so caller-specified GGUF options likefirst_conversionnever reached export.Commit: 0029c54
VLM GGUF exports could leave same-name vision tensors in MLX layout, e.g. Gemma3 patch embeddings stayed OHWI instead of OIHW
Root cause: VLM sanitizer inversion only considered renamed candidates; for models whose sanitizer leaves a tensor name unchanged but changes layout, replay could accept the unchanged name/tensor and stop before applying the needed layout transform.
Commit: 97c980f
VLM GGUF sanitizer replay failed for models whose sanitizers need real submodules, e.g. GLM-OCR
Root cause: Sanitizer replay used a fake proxy object with only
config/args, but model sanitizers such as GLM-OCR call submodule sanitizers through real attributes likeself.vision_tower, so replay failed and no tensor rewrite happened.Commit: c7002e7
VLM GGUF export could fail with modern package-layout llama.cpp converters due missing
conversion/sibling packageRoot cause: Modern llama.cpp conversion scripts are package entrypoints that import sibling
conversion.*modules; writing/running the patched script from a detached cache directory broke that package layout, and the MLX path could also use a different llama.cpp tree than the shared patcher root.Commit: 4546225
MLX VLM loading could degrade to tokenizer-only processors, so GGUF staging omitted image processor metadata needed by mmproj conversion
Root cause: GLM-OCR custom processor construction could fail through Transformers
AutoImageProcessorwithouttorchvision, and mlx-vlm fell back to tokenizer-only metadata.Commit: 398cb9c
VLM GGUF staging could omit image processor sidecars like
preprocessor_config.json, breaking mmproj conversionRoot cause: MLX merged saves trusted tokenizer/model saves to emit every non-weight sidecar needed by downstream converters, but degraded or tokenizer-only processors can omit source files such as
preprocessor_config.json,processor_config.json,video_preprocessor_config.json, tokenizer model files, or custom processing files.Commit: df5deb9
GLM-OCR GGUF exports could advertise a NextN/MTP layer that the MLX model did not export, making llama.cpp load fail on missing
blk.16.*tensorsRoot cause: Source config metadata could advertise speculative/NextN/MTP layers through fields such as
num_nextn_predict_layers,nextn_predict_layers, ormtp_num_hidden_layers, but the loaded/exported MLX language model did not contain those extra blocks.Commit: d6d5d39
Raw mlx-vlm model saves could miss
config.jsonbecausemodel.configdataclass configs were not extractedRoot cause:
_get_model_configonly handled_configdictionaries andmodel.args, while raw mlx-vlm models can expose config as a dataclass-likemodel.config.Commit: 962dec8
Validation:
python -m pytest tests/test_mlx_save_export_regressions.py -q.