Skip to content

Studio: exclude mlx-lm 0.31.3 (broke gemma4/qwen3_5 QK-norm load on Apple Silicon)#6803

Open
danielhanchen wants to merge 5 commits into
mainfrom
studio-mlx-arch-version-gate
Open

Studio: exclude mlx-lm 0.31.3 (broke gemma4/qwen3_5 QK-norm load on Apple Silicon)#6803
danielhanchen wants to merge 5 commits into
mainfrom
studio-mlx-arch-version-gate

Conversation

@danielhanchen

Copy link
Copy Markdown
Member

Problem

MLX loads of gemma4 / qwen3_5 checkpoints (e.g. unsloth/gemma-4-E2B-it-UD-MLX-4bit) fail with:

Failed to load model: Received 140 parameters not in model:
language_model.model.layers.15.self_attn.k_norm.weight, ...

These are QK-norm architectures (q_norm / k_norm). mlx-lm 0.31.3 regressed their strict load_weights, and Studio installs the MLX stack unpinned at latest, so it pulls the broken release.

Root cause (reproduced on real Apple Silicon)

A macos-14 sweep across mlx-lm builds:

Build mlx-lm gemma4 qwen3_5 llama (control)
latest PyPI (what Studio installs today) 0.31.3 FAIL (parameters not in model) pass pass
pinned 0.31.2 pass pass pass
git-main (future 0.31.4) 0.31.3+git pass pass pass

The failure is exactly mlx-lm 0.31.3; 0.31.2 and git-main both load and generate coherently. Matches mlx-lm #1242.

Fix

Exclude only the known-bad release (!=0.31.3) so --upgrade still resolves to the newest good build (0.31.2 today, >=0.31.4 later):

  • install_python_stack.py: the Apple Silicon MLX install now requests mlx-lm!=0.31.3.
  • mlx_repair.py: the self-heal floor excludes 0.31.3, and an already-installed 0.31.3 is treated as unsatisfied so the self-heal replaces it.

Validation

  • The macos-14 pinned job proves 0.31.2 loads + generates coherently for gemma4, qwen3_5 and the llama control.
  • A macos-14 job installing with the shipped constraint (--upgrade mlx mlx-metal "mlx-lm!=0.31.3" mlx-vlm) confirms it resolves to a good build and all three models load.
  • pytest tests/test_mlx_repair.py (21 passing, 2 new): the install spec excludes 0.31.3, and an installed 0.31.3 triggers the self-heal.

GGUF equivalents of the same checkpoints load and generate fine, so the models are healthy; this is purely an mlx-lm arch/version gap.

mlx-lm 0.31.3 regressed the QK-norm archs: its strict load_weights rejects the
q_norm/k_norm tensors with "Received N parameters not in model", so gemma4 and
qwen3_5 checkpoints fail to load. Studio installs the MLX stack unpinned at
latest, which pulls 0.31.3. Verified on a real macos-14 runner: gemma4 fails to
load on 0.31.3 but loads and generates coherently on 0.31.2 and on git-main
(future 0.31.4). See mlx-lm #1242.

Exclude just that release (!=0.31.3) in the installer and the self-heal floor so
--upgrade still resolves to the newest good build, and treat an already-installed
0.31.3 as unsatisfied so the self-heal replaces it.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request excludes the regressed mlx-lm version 0.31.3 (which broke QK-norm architectures) from installation and triggers a self-heal repair if it is already installed. The reviewer suggests using packaging.version.Version objects instead of raw string matching when checking for bad installed versions to prevent fragility with semantically identical version strings.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread studio/backend/utils/mlx_repair.py Outdated
Comment on lines 156 to 162
installed = _dist_version(name)
if Version(installed) < Version(minimum):
return False
# A known-broken build (e.g. mlx-lm 0.31.3, QK-norm load) counts as
# unsatisfied so the self-heal reinstalls a good one.
if installed in _MLX_BAD_VERSIONS.get(name, ()):
return False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Comparing package versions using raw string matching (installed in _MLX_BAD_VERSIONS.get(name, ())) can be fragile. Version strings can have different representations (such as trailing zeros, local build tags, or post-releases) that are semantically identical but syntactically different. Since packaging.version.Version is already imported and used in this function, it is safer and more robust to parse the installed version once and perform the comparison using Version objects.

Suggested change
installed = _dist_version(name)
if Version(installed) < Version(minimum):
return False
# A known-broken build (e.g. mlx-lm 0.31.3, QK-norm load) counts as
# unsatisfied so the self-heal reinstalls a good one.
if installed in _MLX_BAD_VERSIONS.get(name, ()):
return False
installed = _dist_version(name)
installed_version = Version(installed)
if installed_version < Version(minimum):
return False
# A known-broken build (e.g. mlx-lm 0.31.3, QK-norm load) counts as
# unsatisfied so the self-heal reinstalls a good one.
if any(installed_version == Version(bad) for bad in _MLX_BAD_VERSIONS.get(name, ())):
return False

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c36248e9a8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"mlx",
"mlx-metal",
"mlx-lm",
f"mlx-lm{MLX_LM_BAD_VERSION_EXCLUSION}",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cover the fresh install path too

On the normal install.sh Apple Silicon install path, install.sh first runs uv pip install --python "$_VENV_PY" -- "$PACKAGE_NAME" (lines 2899-2900) and then invokes setup.sh with SKIP_STUDIO_BASE=1 (lines 3010 and 3042), so this guarded MLX stack step is skipped entirely. That means the new mlx-lm!=0.31.3 constraint only protects standalone unsloth studio update / non-skip-base runs; fresh installs can still resolve the transitive MLX dependency to the known-bad 0.31.3 and hit the same QK-norm load failure until the best-effort runtime autorepair succeeds. Please add the same exclusion to the install.sh unsloth install path or ensure this MLX repair step runs when skip_base is set on Apple Silicon.

Useful? React with 👍 / 👎.

Address PR review:
- Fresh install.sh (Apple Silicon) runs the base 'uv pip install unsloth' with
  SKIP_STUDIO_BASE=1, skipping the guarded MLX-stack step, so transitive
  resolution could still pull mlx-lm 0.31.3. install.sh already exports
  UV_OVERRIDE -> overrides-darwin-arm64.txt before that install, so exclude
  mlx-lm 0.31.3 there too; this also strengthens the self-heal (same override).
- Match the known-bad version with parsed packaging.Version so 0.31.3 == 0.31.3.0
  (trailing-zero normalization) instead of raw string equality.
@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@danielhanchen

Copy link
Copy Markdown
Member Author

Thanks, both addressed in aaee1b2:

  • Fresh install path: install.sh already exports UV_OVERRIDE -> overrides-darwin-arm64.txt on Apple Silicon before the base uv pip install unsloth, so I added mlx-lm!=0.31.3 to that override file. That excludes the bad release from transitive resolution on the fresh (SKIP_STUDIO_BASE=1) path too, and it also flows through the self-heal (which points UV_OVERRIDE at the same file). The explicit mlx-lm!=0.31.3 in the guarded install step stays for the update / non-skip-base path.
  • Bad-version check now compares parsed packaging.version.Version objects, so 0.31.3 and 0.31.3.0 both match. A local/post rebuild is intentionally not flagged since it may carry the fix.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aaee1b249e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# resolution too -- the fresh install.sh path pulls mlx-lm via unsloth-zoo before
# the guarded MLX-stack install step runs. 0.31.2 and >=0.31.4 are fine; see
# mlx-lm #1242.
mlx-lm!=0.31.3

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cover fresh curl installs when excluding mlx-lm

This override does not protect the fresh Apple Silicon installer path described in the comment: install.sh only exports UV_OVERRIDE when the repo-relative overrides file already exists before the initial uv pip install -- "$PACKAGE_NAME", which is not true for the common curl-piped/downloaded installer, and the later install_python_stack.py MLX step is skipped because install.sh sets SKIP_STUDIO_BASE=1. In that environment the transitive mlx-lm resolution can still pick 0.31.3 and leave Gemma/Qwen MLX loads broken until autorepair succeeds, so the exclusion needs to be generated/applied directly in the fresh install.sh install path too.

Useful? React with 👍 / 👎.

The overrides file only applies via UV_OVERRIDE when it exists relative to the
script, which is not true for a curl-piped install, and the guarded MLX step in
install_python_stack.py is skipped there (SKIP_STUDIO_BASE=1). So the base
install could still resolve the transitive mlx-lm to the broken 0.31.3. Append
mlx-lm!=0.31.3 to the base install on Apple Silicon (empty elsewhere), so the
fresh path pins away from 0.31.3 without waiting for the runtime self-heal.
@danielhanchen

Copy link
Copy Markdown
Member Author

Covered the fresh curl-install path in ce773ed: install.sh now appends mlx-lm!=0.31.3 to the base Apple Silicon install (empty elsewhere), so a curl-piped install pins away from 0.31.3 even when the overrides file is not present relative to the script and the guarded install_python_stack.py MLX step is skipped (SKIP_STUDIO_BASE=1). The overrides-file exclusion still covers the --local / update paths, and the self-heal remains the last resort.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ce773ed3ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread install.sh
else
run_install_cmd_retry "install unsloth" uv pip install --python "$_VENV_PY" \
--upgrade-package unsloth -- "$PACKAGE_NAME"
--upgrade-package unsloth -- "$PACKAGE_NAME" ${_MLX_LM_EXCLUDE_ARG:-}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply the MLX exclusion on migrations too

This appends mlx-lm!=0.31.3 only on the fresh non-migrated install path. In the legacy migration path above, a healthy $STUDIO_HOME/.venv is moved into $VENV_DIR, _MIGRATED=true, and the installer runs the separate install unsloth (migrated) command without this extra requirement before invoking setup with SKIP_STUDIO_BASE=1; for curl-piped Apple Silicon migrations there is also no repo-relative overrides file to export. That path can still resolve a missing/transitive mlx-lm to the known-bad 0.31.3 and leave QK-norm MLX models broken until runtime autorepair succeeds.

Useful? React with 👍 / 👎.

# resolution too -- the fresh install.sh path pulls mlx-lm via unsloth-zoo before
# the guarded MLX-stack install step runs. 0.31.2 and >=0.31.4 are fine; see
# mlx-lm #1242.
mlx-lm!=0.31.3

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the MLX floor in the override

UV overrides are absolute replacements rather than additive constraints, so this entry replaces any transitive mlx-lm>=... requirement with only mlx-lm!=0.31.3. On Apple Silicon paths that set UV_OVERRIDE from this file, an already-installed or resolver-backtracked mlx-lm<0.22.0 can now satisfy the overridden requirement even though mlx_repair.py still treats that as below the usable MLX stack floor, leaving Train/Export chat-only until runtime repair runs. Please include the same floor here, e.g. mlx-lm>=0.22.0,!=0.31.3, so the override only removes the bad release.

Useful? React with 👍 / 👎.

…2.0 floor

The with-deps migrated install did not append ${_MLX_LM_EXCLUDE_ARG:-}, so a
curl-piped Apple Silicon migration (no repo overrides file, UV_OVERRIDE unset)
could resolve mlx-lm 0.31.3 transitively. Append the exclusion there, matching
the fresh install path. The no-torch migration is left alone since --no-deps
never resolves mlx-lm (same as the fresh no-torch path).

Also restore the >=0.22.0 floor in overrides-darwin-arm64.txt: a uv override
replaces the transitive constraint, so a bare !=0.31.3 could let the resolver
drop below the supported minimum that mlx_repair.py enforces at runtime.
@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: 82e2ee2291

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 82e2ee2291

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: 82e2ee2291

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 4847bbdb4e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: 4847bbdb4e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: 4847bbdb4e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: 4847bbdb4e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant