Skip to content

Version 0.6.0

Version 0.6.0 #286

Workflow file for this run

name: CI
on:
push:
branches: [main]
tags: ['[0-9]+.[0-9]+.[0-9]+*']
pull_request:
branches: [main]
workflow_dispatch:
inputs:
run_full_tests:
description: 'Run full model tests (whisper_full, vit_full, distilbert_full, training_full, fast_kernels_full)'
type: boolean
default: true
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
precommit:
name: precommit (${{ matrix.os }}, variant=${{ matrix.variant }})
if: github.event_name != 'push' || github.ref_type == 'branch'
# AOT prebuilt is built on macos-14 and is the older-macOS-compatible
# path. JIT prebuilt is built on macos-26 and isn't portable back to
# macos-14 (the macOS 26 libSystem grew `__fmaxf16` and other half-
# float intrinsics that MLX's Metal stack ends up referencing). Each
# lane therefore runs on the runner whose SDK the prebuilt targets.
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-14
variant: aot
- os: macos-26
variant: jit
env:
MIX_ENV: test
EMILY_MLX_VARIANT: ${{ matrix.variant }}
# Pin the cache to the path the actions/cache step below
# populates. Without it, mix.exs's macOS default of
# DARWIN_USER_CACHE_DIR (`/private/var/folders/...`) misses
# the restored cache and rebuilds MLX from scratch every run.
# mix.exs runs `Path.expand` on this, so `~` resolves correctly.
EMILY_CACHE: ~/Library/Caches/emily
steps:
- uses: actions/checkout@v6
- name: Setup Beam
uses: erlef/setup-beam@v1
with:
version-file: .tool-versions
version-type: strict
- name: Cache deps
uses: actions/cache@v5
with:
path: deps
key: deps-${{ runner.os }}-${{ hashFiles('mix.lock') }}
restore-keys: deps-${{ runner.os }}-
- name: Cache build
uses: actions/cache@v5
with:
path: _build
key: build-${{ runner.os }}-${{ matrix.variant }}-${{ hashFiles('mix.lock', 'c_src/**', 'Makefile', '.tool-versions', 'mix.exs', 'config/config.exs') }}
restore-keys: build-${{ runner.os }}-${{ matrix.variant }}-
# MLX prebuilt tarball (~40 MB AOT / ~5 MB JIT) + extracted install.
# Keyed by the NIF sources and the pinned MLX version in mix.exs;
# variants are cached separately because the install dir differs.
- name: Cache MLX and NIF objects
uses: actions/cache@v5
with:
path: ~/Library/Caches/emily
key: mlx-${{ runner.os }}-${{ matrix.variant }}-${{ hashFiles('c_src/**', 'Makefile', 'mix.exs') }}
restore-keys: mlx-${{ runner.os }}-${{ matrix.variant }}-
# The Bumblebee cache holds tiny-random HuggingFace fixtures
# downloaded by the conformance suite (~3 MB across 7 repos).
# Cached by conformance test content so new fixtures invalidate;
# restore-keys lets unrelated changes reuse the existing cache.
- name: Cache Bumblebee fixtures
uses: actions/cache@v5
with:
path: ~/Library/Caches/bumblebee
key: bumblebee-${{ runner.os }}-${{ hashFiles('test/emily/conformance/**') }}
restore-keys: bumblebee-${{ runner.os }}-
- run: mix deps.get
- run: mix precommit
# Conformance tests are excluded from the default suite because
# they require network access on a cold cache (see
# test/test_helper.exs). In CI we always want them green — a
# DistilBERT forward pass is the canonical integration signal
# that no Nx op on the transformer critical path has regressed.
- run: mix test --only conformance
# Manual-only job: exercises the heavy `*_full` conformance and
# training variants excluded from the default suite (see
# test/test_helper.exs). These download full-size checkpoints
# (hundreds of MB) and take multi-minute wall time, so they run
# on demand via the Actions "Run workflow" button rather than on
# every push / PR.
full-tests:
name: full model tests (${{ matrix.os }}, variant=${{ matrix.variant }})
if: github.event_name == 'workflow_dispatch' && inputs.run_full_tests
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-14
variant: aot
- os: macos-26
variant: jit
env:
MIX_ENV: test
EMILY_MLX_VARIANT: ${{ matrix.variant }}
# Pin the cache to the path the actions/cache step below
# populates. Without it, mix.exs's macOS default of
# DARWIN_USER_CACHE_DIR (`/private/var/folders/...`) misses
# the restored cache and rebuilds MLX from scratch every run.
# mix.exs runs `Path.expand` on this, so `~` resolves correctly.
EMILY_CACHE: ~/Library/Caches/emily
steps:
- uses: actions/checkout@v6
- name: Setup Beam
uses: erlef/setup-beam@v1
with:
version-file: .tool-versions
version-type: strict
- name: Cache deps
uses: actions/cache@v5
with:
path: deps
key: deps-${{ runner.os }}-${{ hashFiles('mix.lock') }}
restore-keys: deps-${{ runner.os }}-
- name: Cache build
uses: actions/cache@v5
with:
path: _build
key: build-${{ runner.os }}-${{ matrix.variant }}-${{ hashFiles('mix.lock', 'c_src/**', 'Makefile', '.tool-versions', 'mix.exs', 'config/config.exs') }}
restore-keys: build-${{ runner.os }}-${{ matrix.variant }}-
- name: Cache MLX and NIF objects
uses: actions/cache@v5
with:
path: ~/Library/Caches/emily
key: mlx-${{ runner.os }}-${{ matrix.variant }}-${{ hashFiles('c_src/**', 'Makefile', 'mix.exs') }}
restore-keys: mlx-${{ runner.os }}-${{ matrix.variant }}-
# Full checkpoints land here (Bumblebee + scidata MNIST).
- name: Cache Bumblebee fixtures
uses: actions/cache@v5
with:
path: ~/Library/Caches/bumblebee
key: bumblebee-full-${{ runner.os }}-${{ hashFiles('test/emily/conformance/**', 'test/emily/bumblebee/**') }}
restore-keys: |
bumblebee-full-${{ runner.os }}-
bumblebee-${{ runner.os }}-
- run: mix deps.get
- run: mix compile
- name: whisper_full
run: mix test --only whisper_full
- name: vit_full
run: mix test --only vit_full
- name: distilbert_full
run: mix test --only distilbert_full
- name: training_full
run: mix test --only training_full
- name: fast_kernels_full
run: mix test --only fast_kernels_full
# ASan CI deferred: requires OTP built with --enable-sanitizers=address
# (macOS SIP blocks DYLD_INSERT_LIBRARIES, and late-loaded libasan
# fails). See Makefile and RELEASE.md for details.