Studio: exclude mlx-lm 0.31.3 (broke gemma4/qwen3_5 QK-norm load on Apple Silicon) (#6803)
* Studio: exclude mlx-lm 0.31.3 (broke gemma4/qwen3_5 QK-norm load) 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. * Studio MLX: cover fresh-install path + robust bad-version compare 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. * Studio: exclude mlx-lm 0.31.3 on the fresh Apple Silicon install too 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. * Studio: exclude mlx-lm 0.31.3 on the migrated install; keep the >=0.22.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. * Triage huggingface_hub 1.22.0 / fastapi / multiprocess scanner false positives The scan-packages gate red-failed on all three shards after transitive deps bumped. Every new CRITICAL is a benign false positive, verified against upstream: - huggingface_hub 1.22.0 added _sandbox.py for the remote HF sandbox feature. Its job-startup bootstrap string (fetch sbx-server into the container /tmp and exec it) and the SandboxPool host-reservation loop trip the staged-dropper and C2-loop heuristics; that script runs inside a remote HF container, not on the user machine. The bump also re-hashed the already-reviewed benign polling loops in hf_api.py and utils/_http.py. The PyPI artifact is byte-identical to the official v1.22.0 tag. - fastapi 0.139.0 routing.py re-hashed the websocket keepalive while-True loop; byte-identical to upstream 0.139.0. - multiprocess 0.70.19 forkserver.py and tests/__init__.py re-hashed the AF_UNIX fork-server IPC and fd-inheritance tests; genuine uqfoundation release, local IPC not network. Added 7 reviewed allowlist entries (no blind regenerate). All three shards (hf-stack, studio, extras) exit 0 locally. * Tighten mlx-lm 0.31.3 exclusion comments * Trim mlx-lm 0.31.3 exclusion comments
This commit is contained in:
parent
f109e7f0e6
commit
c2a7b78f6b
6 changed files with 81 additions and 5 deletions
12
install.sh
12
install.sh
|
|
@ -1442,8 +1442,14 @@ if [ "$_NO_TORCH_FLAG" = true ] || [ "$MAC_INTEL" = true ]; then
|
|||
SKIP_TORCH=true
|
||||
fi
|
||||
|
||||
# Apple Silicon: exclude broken mlx-lm 0.31.3 (QK-norm load regression for
|
||||
# gemma4 / qwen3_5; mlx-lm #1242). A curl-piped install has no overrides file
|
||||
# and skips the guarded MLX step (SKIP_STUDIO_BASE=1), so this is the only cover.
|
||||
_MLX_LM_EXCLUDE_ARG=""
|
||||
|
||||
# Apple Silicon: override mlx-vlm / mlx-lm's transformers pin (see overrides file).
|
||||
if [ "$OS" = "macos" ] && [ "$_ARCH" = "arm64" ]; then
|
||||
_MLX_LM_EXCLUDE_ARG="mlx-lm!=0.31.3"
|
||||
_OVERRIDES_FILE="$(cd "$(dirname "$0" 2>/dev/null || echo ".")" && pwd)/studio/backend/requirements/single-env/overrides-darwin-arm64.txt"
|
||||
if [ -f "$_OVERRIDES_FILE" ]; then
|
||||
# uv splits UV_OVERRIDE on whitespace, so a repo path with whitespace
|
||||
|
|
@ -2679,9 +2685,11 @@ if [ "$_MIGRATED" = true ]; then
|
|||
run_install_cmd_retry "install no-torch runtime deps" uv pip install --python "$_VENV_PY" --no-deps -r "$_NO_TORCH_RT"
|
||||
fi
|
||||
else
|
||||
# Pin mlx-lm away from 0.31.3 here too: a curl-piped migration has no
|
||||
# overrides file, so UV_OVERRIDE is unset and this positional is the only cover.
|
||||
run_install_cmd_retry "install unsloth (migrated)" uv pip install --python "$_VENV_PY" \
|
||||
--reinstall-package unsloth --reinstall-package unsloth-zoo \
|
||||
"unsloth>=2026.6.9" "unsloth-zoo>=2026.6.7"
|
||||
"unsloth>=2026.6.9" "unsloth-zoo>=2026.6.7" ${_MLX_LM_EXCLUDE_ARG:-}
|
||||
fi
|
||||
if [ "$STUDIO_LOCAL_INSTALL" = true ]; then
|
||||
substep "overlaying local repo (editable)..."
|
||||
|
|
@ -2912,7 +2920,7 @@ elif [ -n "$TORCH_INDEX_URL" ]; then
|
|||
"unsloth-zoo @ git+https://github.com/unslothai/unsloth-zoo"
|
||||
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:-}
|
||||
fi
|
||||
# AMD ROCm: repair torch if the unsloth/unsloth-zoo install pulled in
|
||||
# CUDA torch from PyPI, overwriting the ROCm wheels installed in Step 1.
|
||||
|
|
|
|||
|
|
@ -1545,6 +1545,22 @@
|
|||
"severity": "HIGH",
|
||||
"evidence": "Obfusc: L52: compiled = compile(source=pysrc, filename=filename, mode='exec')\nExec: L53: exec(compiled, globs, globs)",
|
||||
"evidence_hash": "c429e4c977a61db6b7c717b5a552fce74eda622213e49eb5467a3782fd746fb9"
|
||||
},
|
||||
{
|
||||
"package": "multiprocess",
|
||||
"file": "multiprocess/forkserver.py",
|
||||
"check": "Reverse shell / bind shell pattern",
|
||||
"severity": "CRITICAL",
|
||||
"evidence": "L6: import socket sha256:6c707119169286c9a798e2c8d13a48614e481d8a503950916fd4ffb4c94d3182",
|
||||
"evidence_hash": "50fec0f0522a8e4e636bf348b752002d7935d8455af31fb78c6f11e2eba19f6d"
|
||||
},
|
||||
{
|
||||
"package": "multiprocess",
|
||||
"file": "multiprocess/tests/__init__.py",
|
||||
"check": "Reverse shell / bind shell pattern",
|
||||
"severity": "CRITICAL",
|
||||
"evidence": "L3569: os.dup2(conn.fileno(), i) | L3601: \"test needs os.dup2()\") | L3619: os.dup2(fd, newfd) | L20: import socket sha256:c824dc0f409f242420c3fbb324790c53cb3078d2c8b07ee8f2a05694b01c2946",
|
||||
"evidence_hash": "3878a2b430c175dbc5877a95195bfe52f9588ff73fb74e2261ed5e33087915ad"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,3 +10,9 @@ transformers>=4.57.6
|
|||
# anyio that also ImportErrors on TaskHandle and 500s the server. An override
|
||||
# wins the fight, so force one consistent <4.14 here too.
|
||||
anyio<4.14.0
|
||||
|
||||
# mlx-lm 0.31.3 regressed QK-norm archs (gemma4 / qwen3_5): strict load_weights
|
||||
# rejects q_norm/k_norm, so those checkpoints fail to load. mlx-lm #1242.
|
||||
# The override also drops it from transitive resolution; keep the >=0.22.0 floor
|
||||
# (mirrors mlx_repair.py _MLX_MIN_VERSIONS) or the resolver could go below it.
|
||||
mlx-lm>=0.22.0,!=0.31.3
|
||||
|
|
|
|||
|
|
@ -271,6 +271,29 @@ def test_stack_available_requires_runtime_imports_and_versions(monkeypatch):
|
|||
assert imported == list(mr._MLX_RUNTIME_IMPORTS)
|
||||
|
||||
|
||||
def test_mlx_packages_exclude_known_bad_mlx_lm():
|
||||
# mlx-lm 0.31.3 regressed QK-norm archs (gemma4 / qwen3_5); the install spec
|
||||
# must exclude it so the resolver picks 0.31.2 or >=0.31.4. See mlx-lm #1242.
|
||||
(mlx_lm_spec,) = [p for p in mr.MLX_PACKAGES if p.startswith("mlx-lm")]
|
||||
assert mlx_lm_spec == "mlx-lm>=0.22.0,!=0.31.3"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bad_form", ["0.31.3", "0.31.3.0"])
|
||||
def test_known_bad_installed_mlx_lm_triggers_repair(monkeypatch, bad_form):
|
||||
# An installed 0.31.3 counts as unsatisfied so the self-heal replaces it;
|
||||
# parsed-Version compare also catches the trailing-zero form 0.31.3.0.
|
||||
import importlib.metadata as metadata
|
||||
|
||||
def _version(name):
|
||||
return bad_form if name == "mlx-lm" else mr._MLX_MIN_VERSIONS[name]
|
||||
|
||||
monkeypatch.setattr(metadata, "version", _version)
|
||||
monkeypatch.setattr(
|
||||
mr.importlib, "import_module", lambda _n: pytest.fail("versions must gate imports")
|
||||
)
|
||||
assert mr.mlx_stack_available() is False
|
||||
|
||||
|
||||
def test_no_op_off_apple_silicon(monkeypatch):
|
||||
monkeypatch.setattr(mr, "is_apple_silicon", lambda: False)
|
||||
called = {"n": 0}
|
||||
|
|
|
|||
|
|
@ -45,9 +45,21 @@ DISABLE_ENV_VAR = "UNSLOTH_DISABLE_MLX_AUTOREPAIR"
|
|||
# deps). mlx-vlm especially must be >=0.4.4: an older one still imports but
|
||||
# breaks VLM Train/Export, so installing it would wrongly clear chat-only.
|
||||
_MLX_MIN_VERSIONS = {"mlx": "0.22.0", "mlx-lm": "0.22.0", "mlx-vlm": "0.4.4"}
|
||||
# mlx-lm 0.31.3 regressed QK-norm archs (gemma4 / qwen3_5): strict load_weights
|
||||
# rejects q_norm/k_norm, so a self-heal must not pull it. mlx-lm #1242.
|
||||
_MLX_BAD_VERSIONS = {"mlx-lm": ("0.31.3",)}
|
||||
_MLX_PACKAGE_NAMES = tuple(_MLX_MIN_VERSIONS)
|
||||
_MLX_RUNTIME_IMPORTS = ("mlx.core", "mlx_lm", "mlx_lm.sample_utils", "mlx_vlm")
|
||||
MLX_PACKAGES = tuple(f"{name}>={version}" for name, version in _MLX_MIN_VERSIONS.items())
|
||||
|
||||
|
||||
def _mlx_spec(name: str, version: str) -> str:
|
||||
spec = f"{name}>={version}"
|
||||
for bad in _MLX_BAD_VERSIONS.get(name, ()):
|
||||
spec += f",!={bad}"
|
||||
return spec
|
||||
|
||||
|
||||
MLX_PACKAGES = tuple(_mlx_spec(name, version) for name, version in _MLX_MIN_VERSIONS.items())
|
||||
_MLX_REINSTALL_ARGS = tuple(
|
||||
arg for name in _MLX_PACKAGE_NAMES for arg in ("--reinstall-package", name)
|
||||
)
|
||||
|
|
@ -140,7 +152,12 @@ def _mlx_versions_satisfy_minimums() -> bool:
|
|||
return False
|
||||
for name, minimum in _MLX_MIN_VERSIONS.items():
|
||||
try:
|
||||
if Version(_dist_version(name)) < Version(minimum):
|
||||
installed = Version(_dist_version(name))
|
||||
if installed < Version(minimum):
|
||||
return False
|
||||
# A known-broken build counts as unsatisfied so the self-heal
|
||||
# reinstalls a good one; Version compare matches 0.31.3(.0/+local).
|
||||
if any(installed == Version(bad) for bad in _MLX_BAD_VERSIONS.get(name, ())):
|
||||
return False
|
||||
except PackageNotFoundError:
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -1532,6 +1532,10 @@ LOCAL_DD_UNSTRUCTURED_PLUGIN = (
|
|||
)
|
||||
LOCAL_DD_GITHUB_PLUGIN = SCRIPT_DIR / "backend" / "plugins" / "data-designer-github-repo-seed"
|
||||
|
||||
# mlx-lm 0.31.3 broke gemma4 / qwen3_5 loading (strict load_weights rejects the
|
||||
# QK-norm q_norm/k_norm tensors); exclude just that release. See mlx-lm #1242.
|
||||
MLX_LM_BAD_VERSION_EXCLUSION = "!=0.31.3"
|
||||
|
||||
# Apple Silicon: override mlx-vlm/mlx-lm's transformers pin (see overrides).
|
||||
# _uv_safe_path: uv truncates UV_OVERRIDE at the first space too (issue #6503).
|
||||
_MLX_OVERRIDES = SINGLE_ENV / "overrides-darwin-arm64.txt"
|
||||
|
|
@ -2092,6 +2096,8 @@ def install_python_stack() -> int:
|
|||
|
||||
# macOS arm64: install MLX stack at latest (UV_OVERRIDE relaxes the
|
||||
# mlx-vlm / mlx-lm transformers pin -- set at module load).
|
||||
# Exclude mlx-lm 0.31.3 (see MLX_LM_BAD_VERSION_EXCLUSION); it broke
|
||||
# gemma4 / qwen3_5 QK-norm loading. mlx-lm #1242.
|
||||
if IS_MAC_ARM and not skip_base:
|
||||
_progress("MLX stack (Apple Silicon)")
|
||||
pip_install(
|
||||
|
|
@ -2100,7 +2106,7 @@ def install_python_stack() -> int:
|
|||
"--upgrade",
|
||||
"mlx",
|
||||
"mlx-metal",
|
||||
"mlx-lm",
|
||||
f"mlx-lm{MLX_LM_BAD_VERSION_EXCLUSION}",
|
||||
"mlx-vlm",
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue