Pin every explicit pip install to an exact released version (latest as of 2026-05-07 within each project's existing constraint range) to reduce supply-chain surface and make rebuilds reproducible. unsloth-zoo on Linux is the pinned PyPI release; on macOS it stays on git main (PR-A is not yet on PyPI). Also fold the previously separate mlx-dispatch (Linux) and mlx-real-apple-silicon (macOS) jobs into a single matrix job with labels linux-cpu-spoof and macos-m1-real, sharing the dispatch test step so adding new MLX dispatch tests applies to both runners automatically. The Mac-only smoke steps (verify _IS_MLX flips True on real Apple Silicon, smoke-import every PR-A MLX-only module) remain gated on if: matrix.real_mlx. Validated locally against .macsim_venv3 with the pinned package set: 35 passed + 1 skipped, matching the prior unpinned run.
250 lines
11 KiB
YAML
250 lines
11 KiB
YAML
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
|
|
|
|
# Focused PR gate for the MLX dispatch surface. One job, two matrix
|
|
# variants:
|
|
#
|
|
# - linux-cpu-spoof ubuntu-latest with hardware probes spoofed via
|
|
# monkeypatch (no Apple Silicon, no real GPU, no
|
|
# real MLX install required).
|
|
# - macos-m1-real macos-14 (M1, 3 vCPU / 7 GB / Apple Silicon
|
|
# standard runner -- FREE for public repositories
|
|
# per the GitHub Actions billing reference, larger
|
|
# variants like macos-14-large/-xlarge are paid
|
|
# so we deliberately avoid those). Adds two
|
|
# Mac-only steps before the dispatch tests:
|
|
# 1. verify unsloth._IS_MLX flips True on real
|
|
# Apple Silicon (no spoof);
|
|
# 2. smoke-import every PR-A MLX-only module
|
|
# (mlx_loader, mlx_trainer, mlx_compile,
|
|
# mlx_utils, mlx_cce, gated_delta_vjp). Each
|
|
# does `import mlx.core as mx` at module top
|
|
# level, so this catches a future change that
|
|
# breaks the real `mlx` PyPI wheel without
|
|
# needing a Mac developer in the loop.
|
|
#
|
|
# Both variants then run the SAME three dispatch test files documented
|
|
# in tests/studio/README.md:
|
|
# - test_hardware_dispatch_matrix.py parametrized 7-profile matrix
|
|
# + 2 dispatch-priority canaries
|
|
# - test_is_mlx_dispatch_gate.py AST + runtime guard on
|
|
# unsloth._IS_MLX
|
|
# - test_mlx_training_worker_behaviors.py AST contract checks on
|
|
# studio/backend/core/training/worker.py
|
|
#
|
|
# Surfaces two PR checks ("MLX dispatch (linux-cpu-spoof)" and
|
|
# "MLX dispatch (macos-m1-real)") sharing one workflow definition so
|
|
# adding new dispatch tests applies to both runners automatically.
|
|
#
|
|
# Security audit footprint: every package this workflow installs is
|
|
# already covered by .github/workflows/security-audit.yml -- the deps
|
|
# come from studio/backend/requirements/studio.txt and unsloth-zoo's
|
|
# pyproject (resolved transitively). The git+ install of unsloth-zoo
|
|
# is intentionally skipped by the audit (pip-audit cannot resolve a
|
|
# git URL through PyPI metadata; the audit comment in security-audit.yml
|
|
# documents this). No new package is introduced solely by MLX CI.
|
|
|
|
name: MLX CI
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- 'unsloth/__init__.py'
|
|
- 'unsloth/_gpu_init.py'
|
|
- 'studio/backend/utils/hardware/**'
|
|
- 'studio/backend/core/training/worker.py'
|
|
- 'studio/backend/core/inference/mlx_inference.py'
|
|
- 'tests/studio/test_hardware_dispatch_matrix.py'
|
|
- 'tests/studio/test_is_mlx_dispatch_gate.py'
|
|
- 'tests/studio/test_mlx_training_worker_behaviors.py'
|
|
- 'tests/conftest.py'
|
|
- '.github/workflows/mlx-ci.yml'
|
|
push:
|
|
branches: [main, pip]
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
dispatch:
|
|
name: MLX dispatch (${{ matrix.label }})
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: ${{ matrix.timeout }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: ubuntu-latest
|
|
label: linux-cpu-spoof
|
|
real_mlx: false
|
|
timeout: 10
|
|
- os: macos-14
|
|
label: macos-m1-real
|
|
real_mlx: true
|
|
timeout: 15
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
|
|
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: '3.12'
|
|
cache: 'pip'
|
|
|
|
# Linux variant: spoofed hardware. Mirrors the Repo tests (CPU)
|
|
# dep set from studio-backend-ci.yml so unsloth's import chain
|
|
# succeeds on a runner without any GPU. CPU torch from the
|
|
# PyTorch index, transformers + datasets + bitsandbytes from
|
|
# the standard PyPI index, unsloth-zoo from PyPI.
|
|
# All explicit pip installs are version-pinned to a single
|
|
# released version. unsloth-zoo on Linux is the latest PyPI
|
|
# release; on macOS it is sourced from git main (PR-A is not
|
|
# yet on PyPI). The pin set was the latest as of 2026-05-07
|
|
# within each project's existing constraint ranges; bump
|
|
# alongside the rest of the security audit when a new release
|
|
# of any of these lands.
|
|
- name: Install deps (Linux+CPU spoof)
|
|
if: matrix.real_mlx == false
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
pip install -r studio/backend/requirements/studio.txt
|
|
pip install \
|
|
'python-multipart==0.0.27' \
|
|
'aiofiles==25.1.0' \
|
|
'sqlalchemy==2.0.49' \
|
|
'cryptography==48.0.0' \
|
|
'pyyaml==6.0.3' \
|
|
'jinja2==3.1.6' \
|
|
'mammoth==1.12.0' \
|
|
'unpdf==1.0.0' \
|
|
'requests==2.33.1' \
|
|
'typer==0.25.1' \
|
|
'numpy==2.4.4' \
|
|
'pytest==9.0.3' \
|
|
'pytest-asyncio==1.3.0' \
|
|
'httpx==0.28.1'
|
|
pip install --index-url https://download.pytorch.org/whl/cpu \
|
|
'torch==2.10.0' 'torchvision==0.25.0'
|
|
pip install 'transformers==5.5.0'
|
|
pip install 'bitsandbytes==0.49.2'
|
|
pip install 'unsloth_zoo==2026.5.1'
|
|
pip install -e . --no-deps
|
|
|
|
# macOS variant: real Apple Silicon. Install ladder validated
|
|
# locally against a Linux mac-sim venv (platform spoofed +
|
|
# mlx_simulation shim + real datasets/transformers/structlog).
|
|
#
|
|
# 1. studio/backend/requirements/studio.txt brings structlog,
|
|
# fastapi, etc. The hardware probe imports structlog at
|
|
# module top level.
|
|
# 2. Same pytest / numpy / httpx stack as the Linux variant.
|
|
# 3. torch is explicitly installed: unsloth-zoo's pyproject
|
|
# deliberately excludes torch on darwin+arm64 (mlx replaces
|
|
# it for runtime use), but the dispatch tests spoof
|
|
# torch.cuda / torch.xpu / torch.backends.mps via monkeypatch
|
|
# and so the test process needs torch importable. We pull
|
|
# from the PyTorch CPU index for both Linux and macOS so
|
|
# Apple Silicon gets the explicit cpu+MPS arm64 wheel rather
|
|
# than something the default PyPI resolver might pick up.
|
|
# https://download.pytorch.org/whl/cpu hosts
|
|
# torch-x.y.z-cp312-...-macosx_*_arm64.whl alongside the
|
|
# Linux x86_64 wheels.
|
|
# 4. unsloth-zoo from git main (NOT PyPI), WITH deps. PR-A's
|
|
# MLX support landed after the most recent unsloth-zoo PyPI
|
|
# release; the wheel still raises NotImplementedError on
|
|
# Apple Silicon when device_type.get_device_type() runs
|
|
# unguarded. Studio's own install.sh overlays unsloth-zoo
|
|
# from git main for the same reason. Pulling deps lets pip
|
|
# resolve the platform-conditional MLX-only wheels (mlx,
|
|
# mlx-lm, mlx-vlm gated on darwin+arm64 in unsloth-zoo's
|
|
# pyproject) AND the shared deps (datasets, transformers,
|
|
# sentencepiece, ...) that unsloth's MLX branch loads via
|
|
# dataprep/raw_text.py.
|
|
# 5. unsloth -e . --no-deps so the editable install does not
|
|
# fight the unsloth-zoo dep set.
|
|
# See the comment on the Linux variant -- same pin set, with
|
|
# two macOS-specific adjustments: no torchvision (zoo's mlx-vlm
|
|
# replaces it on Apple Silicon), no bitsandbytes (CUDA-only),
|
|
# no transformers explicit pin (zoo's deps already constrain
|
|
# it), and unsloth-zoo from git main rather than PyPI.
|
|
- name: Install deps (macOS real Apple Silicon)
|
|
if: matrix.real_mlx
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
pip install -r studio/backend/requirements/studio.txt
|
|
pip install \
|
|
'python-multipart==0.0.27' \
|
|
'aiofiles==25.1.0' \
|
|
'sqlalchemy==2.0.49' \
|
|
'cryptography==48.0.0' \
|
|
'pyyaml==6.0.3' \
|
|
'jinja2==3.1.6' \
|
|
'mammoth==1.12.0' \
|
|
'unpdf==1.0.0' \
|
|
'requests==2.33.1' \
|
|
'typer==0.25.1' \
|
|
'numpy==2.4.4' \
|
|
'pytest==9.0.3' \
|
|
'pytest-asyncio==1.3.0' \
|
|
'httpx==0.28.1'
|
|
pip install --index-url https://download.pytorch.org/whl/cpu \
|
|
'torch==2.10.0'
|
|
pip install "unsloth_zoo @ git+https://github.com/unslothai/unsloth-zoo"
|
|
pip install -e . --no-deps
|
|
|
|
# Mac-only sanity: confirm _IS_MLX activates on real Apple
|
|
# Silicon hardware with no platform spoof.
|
|
- name: Verify _IS_MLX flips True on real Apple Silicon
|
|
if: matrix.real_mlx
|
|
run: |
|
|
python -c "
|
|
import platform
|
|
assert platform.system() == 'Darwin', platform.system()
|
|
assert platform.machine() == 'arm64', platform.machine()
|
|
import unsloth
|
|
assert unsloth._IS_MLX is True, f'expected _IS_MLX=True on real Apple Silicon, got {unsloth._IS_MLX}'
|
|
print('OK: _IS_MLX activated on real Apple Silicon')
|
|
"
|
|
|
|
# Mac-only sanity: confirm every PR-A MLX-only module loads
|
|
# against real mlx + mlx-lm + mlx-vlm wheels.
|
|
- name: Smoke-import every MLX-only unsloth_zoo module
|
|
if: matrix.real_mlx
|
|
run: |
|
|
python -c "
|
|
import importlib
|
|
for name in [
|
|
'unsloth_zoo.mlx_loader',
|
|
'unsloth_zoo.mlx_trainer',
|
|
'unsloth_zoo.mlx_compile',
|
|
'unsloth_zoo.mlx_utils',
|
|
'unsloth_zoo.mlx_cce',
|
|
'unsloth_zoo.gated_delta_vjp',
|
|
]:
|
|
importlib.import_module(name)
|
|
print('OK:', name)
|
|
from unsloth_zoo.mlx_loader import FastMLXModel
|
|
from unsloth_zoo.mlx_trainer import MLXTrainer, MLXTrainingConfig
|
|
assert hasattr(FastMLXModel, 'from_pretrained')
|
|
print('OK: FastMLXModel + MLXTrainer surface present')
|
|
"
|
|
|
|
# Both variants run the same dispatch tests. The monkeypatch
|
|
# spoofs in test_hardware_dispatch_matrix.py override
|
|
# platform.system / platform.machine / torch.cuda /
|
|
# torch.xpu / torch.backends.mps for each test, so they
|
|
# behave identically on Linux and on real Apple Silicon. The
|
|
# macOS variant additionally proves the spoofs do not collide
|
|
# with the real environment.
|
|
- name: MLX dispatch tests (3 files, 36 tests)
|
|
env:
|
|
PYTHONPATH: ${{ github.workspace }}/studio
|
|
UNSLOTH_COMPILE_DISABLE: '1'
|
|
run: |
|
|
python -m pytest -v --tb=short \
|
|
tests/studio/test_hardware_dispatch_matrix.py \
|
|
tests/studio/test_is_mlx_dispatch_gate.py \
|
|
tests/studio/test_mlx_training_worker_behaviors.py
|