Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Contributing to fusion-mlx

fusion-mlx is currently a single-maintainer project. Contributions of
any size are welcome — this is the most direct way to reduce the
project's bus-factor risk. This guide gets you from clone to first PR.

## What we need most

In rough priority order (see [ROADMAP.md](ROADMAP.md) for the full plan):

1. **Test-debt cleanup** — ~301 test files are quarantined in
`tests/unit/debt_modules.txt` (`collect_ignore`). Rescuing them
(fixing imports, marking integration tests, or deleting truly-dead
ones) is high-leverage and low-risk.
2. **tool_calling parser coverage** — add parsers for Gemma4 / Hermes /
Mistral / MiniMax / ui_tars tool-call formats. One model family per
file under a `tool_parsers/` layout (see existing `tool_calling.py`).
3. **Benchmark data** — run `scripts/benchmark_*.py` on your model and
contribute the JSON to `benchmarks/` so the public matrix grows.
4. **Docs & examples** — modality walkthroughs (video, STS, NER),
migration guides (Ollama → fusion-mlx).

## Setup

Requires macOS / Apple Silicon (MLX-native; no Linux/CUDA target).

```bash
git clone git@github.com:dahai80/fusion-mlx.git
cd fusion-mlx
source .venv/bin/activate # or: python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
```

Optional extras (only install what you use):

```bash
pip install -e ".[audio]" # STT/TTS/STS via mlx-audio
pip install -e ".[image]" # Flux1/Flux2 image gen via mflux-fusion
pip install -e ".[video]" # video backends (opencv/librosa/imageio)
pip install -e ".[mcp]" # MCP server
```

## Running the server

```bash
./start.sh start # starts fusion-mlx (default port, see start.sh)
./start.sh stop
./start.sh status
./start.sh log
```

API auth key and port live in the config; the server listens on
`127.0.0.1` by default. Real-model tests require the server running
(see Testing below).

## Testing

```bash
# full active suite (skips quarantined + real-model tests)
pytest tests/unit -q

# a single module
pytest tests/unit/test_<name>.py -q

# real-model tests (loads actual MLX weights — slow, needs models on disk)
FUSION_MLX_REAL_MODEL_TESTS=1 pytest tests/unit -q
```

CI runs on Python 3.11 / 3.12 / 3.13 (macOS-14). The active test count
is ~377 files (301 quarantined, tracked in
`tests/unit/debt_modules.txt`). **Do not** claim a higher count in
README/badges than what `pytest --collect-only -q | tail -1` reports.

### Rule for failing tests

If you encounter a failing test — even one unrelated to your change —
locate and fix it (or file an issue). Do not leave the suite redder
than you found it.

## Lint & format

CI runs `ruff` + `black`; these must pass before merge.

```bash
ruff check fusion_mlx/ tests/
black --check fusion_mlx/ tests/
# autofix:
ruff check --fix fusion_mlx/ tests/
black fusion_mlx/ tests/
```

Notes:
- `fusion_mlx/patches/` is excluded from lint (upstream-derived vendor
code; linting creates merge churn).
- MLX-family packages (`mlx`, `mlx_lm`, `mlx_vlm`, `mlx_embeddings`) are
pinned as known-third-party in `[tool.ruff.lint.isort]` so isort
classifies them deterministically across environments.
- Indentation in generated code uses multiples of 4. No docstrings in
new code.

## Commit & PR flow

```bash
git checkout -b <type>/<short-desc> # feat/fix/docs/chore/refactor
# ...changes...
git add <files>
git commit -m "<type>(#issue): <subject>"
git push -u fusion-mlx <branch> # NOT origin (that's the homebrew tap)
gh pr create --repo dahai80/fusion-mlx --title "<type>(#issue): <subject>" --body "..."
```

PR checklist:
- [ ] `ruff check` + `black --check` pass
- [ ] `pytest tests/unit -q` is no redder than before
- [ ] CHANGELOG.md entry if user-facing
- [ ] README/docs updated if behavior changed

For upstream-blocking issues (mlx-lm / mlx-vlm limitations), **do not
fabricate a path** — file an issue on the upstream, link it here, and
keep a `raise` that fails visibly with a clear message.

## Code style essentials

- **Fail visibly, not silently** — loud errors over silent fallbacks.
- **Surgical changes** — touch only what your change requires; don't
reformat adjacent code.
- **Convention beats novelty** — match the surrounding file's patterns
even if you prefer another.
- **Logging by default** — new code should log enough to locate problems.

## Releases

See [RELEASE.md](RELEASE.md). Maintainers only.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,23 @@

Drop-in replacement for Ollama / vLLM - runs natively on Metal via MLX

[![Version](https://img.shields.io/badge/v0.5.11-blue.svg)](https://github.com/dahai80/fusion-mlx/releases)
[![Version](https://img.shields.io/pypi/v/fusion-mlx?label=version&color=blue)](https://pypi.org/project/fusion-mlx/)
[![Python](https://img.shields.io/badge/Python-3.11+-3776AB.svg)](https://www.python.org/)
[![License](https://img.shields.io/badge/License-Apache--2.0-green.svg)](LICENSE)
[![Tests](https://img.shields.io/badge/Tests-1200+-success.svg)](tests/)
[![Tests](https://img.shields.io/badge/Tests-431%20files%20%7C%208742%20items-success.svg)](tests/)
[![CI](https://github.com/dahai80/fusion-mlx/actions/workflows/ci.yml/badge.svg)](https://github.com/dahai80/fusion-mlx/actions/workflows/ci.yml)
[![GitHub stars](https://img.shields.io/github/stars/dahai80/fusion-mlx?style=social)](https://github.com/dahai80/fusion-mlx/stargazers)

[English](README.md) | [Chinese](README_CN.md)

[Get Started](#quick-start) · [Download App](https://github.com/dahai80/fusion-mlx/releases) · [Benchmarks](https://bench.dpdns.org/) · [Documentation](docs/)

</div>

> **Scope & maturity**: macOS / Apple Silicon only (MLX-native). Beta —
> single-maintainer project, [seeking contributors](CONTRIBUTING.md). See
> [ROADMAP.md](ROADMAP.md) for the full-modality plan and supported models.

---

## Why fusion-mlx?
Expand Down
90 changes: 90 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Release Process

This document fixes the fusion-mlx release flow so it is repeatable and
not tribal knowledge. Follow it end-to-end for every release.

## Prerequisites

- Push access to `dahai80/fusion-mlx` (main repo) and `dahai80/homebrew-fusion-mlx` (tap).
- Local venv: `cd fusion-mlx && source .venv/bin/activate`.
- A clean `main` (all intended changes merged).

## 1. Bump version

Edit `fusion_mlx/_version.py`:

```python
__version__ = "0.8.13" # MAJOR.MINOR.PATCH, bump per semver
```

## 2. Update CHANGELOG.md

Add a new `## [0.8.13] - YYYY-MM-DD` section at the top. Summarize
merged PRs since the last release (one bullet per PR with `#NNN`).
Keep entries user-facing; link to the audit/issue context only when it
affects behavior.

## 3. Commit & PR

```bash
git checkout -b release/0.8.13
git add fusion_mlx/_version.py CHANGELOG.md
git commit -m "chore: bump version 0.8.12 -> 0.8.13"
# push to fusion-mlx remote (NOT origin, which is the homebrew tap)
git push -u fusion-mlx release/0.8.13
gh pr create --repo dahai80/fusion-mlx --title "release: v0.8.13" --body "..."
```

Merge the PR. **CI note**: the macOS-14 runner recurrently stalls on
the test matrix. If CI hangs, merge with `--squash --admin` (prior
releases all did this). Do not block a release on a stalled runner
once lint passes.

## 4. Tag & GitHub release

```bash
git checkout main && git pull fusion-mlx main
git tag v0.8.13
git push fusion-mlx v0.8.13
gh release create v0.8.13 --repo dahai80/fusion-mlx --title "v0.8.13" --notes-file <(gh release view v0.8.12 --repo dahai80/fusion-mlx --json body -q .body | head -1)
```

Publishing the GitHub release triggers `publish.yml`.

## 5. publish.yml (automatic)

`release: published` fires `publish.yml`, which:

1. `build` job (ubuntu): `uv build` + SHA256 checksums + upload artifacts.
2. `publish` job: uploads to **PyPI** via OIDC trusted publisher (no secret needed).
3. `update-homebrew` job: bumps the formula in `homebrew-fusion-mlx` (the `origin` remote) and opens/merges a PR to the tap.

Watch the run: https://github.com/dahai80/fusion-mlx/actions/workflows/publish.yml

### Known stalls

- The `update-homebrew` job runs on a macOS runner that recurrently
queues. It auto-completes eventually; do not re-trigger manually.

## 6. Verify

- PyPI: https://pypi.org/project/fusion-mlx/ shows the new version.
- Homebrew: `brew install dahai80/fusion-mlx/fusion-mlx` installs it
(or `brew upgrade`). Check the tap formula version matches.
- `pip install fusion-mlx==0.8.13` works clean.

## 7. Hotfix flow

If a shipped release has a critical bug:

1. Branch `release/0.8.14` from the `v0.8.13` tag (not main, if main
has moved on).
2. Cherry-pick the fix.
3. Bump to the next patch, CHANGELOG entry, tag, release as above.

## Quick reference: remotes

| remote | repo | purpose |
|--------|------|---------|
| `fusion-mlx` | `git@github.com:dahai80/fusion-mlx.git` | main repo, PRs, tags, releases |
| `origin` | `git@github.com/dahai80/homebrew-fusion-mlx.git` | homebrew tap (auto-bumped by publish.yml) |
137 changes: 137 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Roadmap

> Last updated 2026-08-08 (v0.8.12). Drives from the catch-up plan at
> [`/Users/dahai/fusion/architecture/fusion-mlx-enhance.md`](file:///Users/dahai/fusion/architecture/fusion-mlx-enhance.md).
>
> fusion-mlx is **Apple Silicon only** and bets on the MLX ecosystem.
> We do not compete with llama.cpp on cross-platform/ggml breadth or
> with rapid-mlx on raw LLM throughput. We compete on **full-modality
> local serving** (image/video/STS/NER/reranker + training + Ollama
> compat) — the capabilities the others structurally don't have.

## Strategic moats (defend & amplify)

These are landed today and unmatched by llama.cpp / rapid-mlx / oMLX:

- **Full-modality engines** — 11 engine classes (LLM/VLM/Embedding/
Reranker/STT/TTS/STS/ImageGen/VideoGen/NER/OCR).
- **Video generation** — 10 native MLX backends + VACE-14B E2E + IP-Adapter
/ControlNet/AnimateDiff adapters.
- **Ollama protocol compat** — the only MLX server with `/api/generate`
`/api/chat` `/api/tags` drop-in.
- **Speculative decoding** — 10 methods incl. EAGLE3 (1.445x measured).
- **Training** — LoRA/DPO/GRPO/Reward + in-place swap + HF→MLX wizard.
- **Paged KV + SSD cold tier** + 3-tier priority scheduling.

## Status legend

- ✅ done · 🚧 in progress · 📋 planned · ⛔ won't do (out of scope)

## Near-term (Phase 0–1, 0–4 weeks)

| Item | Status | Note |
|------|--------|------|
| Classifier Alpha → Beta | ✅ | `pyproject` (1d42d34) |
| Governance docs (RELEASE/CONTRIBUTING/ROADMAP) | ✅ | 1d42d34 |
| README badges + scope/maturity statement | ✅ | 1d42d34 |
| Test-debt cleanup (quarantined → rescue) | 🚧 | 15 reactivated, 8742→9105 items (cf8c1ea); more to rescue |
| tool_parser coverage (boundary bugs) | ✅ | 21 parsers + mlx-lm native; ui_tars 24→6 fail (c4d9af6) |
| Public benchmark harness + ≥10 model reports | 📋 | `benchmarks/`, reuse `admin/benchmark` |
| Model compatibility matrix (public) | ✅ | live table below (this commit) |
| GGUF load guard | ✅ | `engine/gguf_guard.py` (#423, v0.8.12) |
| GGUF→MLX load bridge | 📋 | guard done; runtime weight-mapping bridge next |
| tool_calling parser expansion (Gemma4/Hermes/Mistral/MiniMax/ui_tars) | 📋 | split `tool_calling.py` per family |

## Mid-term (Phase 2, 1–3 months)

| Item | Status | Note |
|------|--------|------|
| Resumable streaming (`/v1/stream` + lookup) | 📋 | like llama.cpp stream_session |
| Spec-decoding metrics (draft accept rate) | 📋 | `/metrics` or `spec_routes` |
| DFly/DSpark maturity convergence | 📋 | ~1.5KB vs dflash 29KB — fill or mark experimental |
| MLA/DSA dedicated KV path | 📋 | for DeepSeek/GLM, in `cache/paged_cache.py` |
| Telemetry framework | 📋 | consent/emit/queue/redact/schema |
| Dependency extras split (`[full]` default) | 📋 | text-only saves ~322MB |
| Sigstore / PEP 740 attestation | 📋 | PyPI provenance |

## Long-term (Phase 3, 3–6 months)

| Item | Status | Note |
|------|--------|------|
| Video benchmark + E2E tests + docs | 📋 | make "video tier-1" a verifiable claim |
| Full-modality as headline positioning | 📋 | README/landing rewrite |
| Ultra-low-bit quant (1.5–2bit / TQ / imatrix) evaluation | 📋 | build or bridge or document the boundary |
| homebrew-core inclusion | 📋 | replace self-maintained tap; needs tests+docs+stable API |

## Out of scope (won't do)

| Item | Why |
|------|-----|
| ⛔ Cross-platform backends (CUDA/Linux/Windows) | MLX-native bet; Apple Silicon is the scope |
| ⛔ Self-built 140-arch model enum / HF converter | follow MLX upstream + GGUF bridge |
| ⛔ Self-built GGUF quantization | llama.cpp is the standard; we load, not quantize |
| ⛔ MXFP8 mixed-precision training | mlx-lm 0.31.3 has no fp8 train path (#425 upstream-blocked); fail-visible raise stays |
| ⛔ Compete on raw LLM throughput vs rapid-mlx | their moat; we win on modality/training/Ollama |

## Supported models (live matrix)

Status: ✅ **Tested** (has a fusion alias, covered by the test suite) ·
🟡 **Custom patch** (runs via vendored `fusion_mlx/patches/` — cutting-edge
arch, may carry caveats) · 🟦 **Upstream** (supported by mlx-lm 0.31.3 /
mlx-vlm 0.5.0, runs but no fusion alias — not individually tested by us) ·
❌ **No** (GGUF rejected with a clear error; or arch not in upstream/vendored).

GGUF files are rejected at load by `engine/gguf_guard.py` (#423, v0.8.12)
with an error pointing at `mlx-community` repos or `POST /v1/convert`.

### Text LLMs (mlx-lm)

| Family | Status | Tool parser | Spec decode | Alias example |
|--------|--------|-------------|-------------|---------------|
| Qwen3 / 3.5 / 3.6 / 3-Coder | ✅ | hermes / qwen3_coder_xml | most | `qwen3.6-27b-4bit` |
| DeepSeek-R1 | ✅ | deepseek | ✅ | `deepseek-r1-7b-4bit` |
| DeepSeek-V3 / V4 | ✅ + 🟡 patch | deepseek / deepseek_v3 | ✅ | `deepseek-v4-27b` |
| Gemma 3 / 4 | ✅ | gemma4 / hermes | ✅ | `gemma-4-4b-4bit` |
| Llama 3 / 4 | ✅ + 🟡 patch (`llama4_attention`) | llama | ✅ | `llama4-8b-4bit` |
| GLM-4 / GLM-MoE | ✅ + 🟡 patch (`glm_moe_dsa`) | glm47 | ✅ | `glm-4-9b-4bit` |
| Phi-3.5 / 4 | ✅ | hermes | ✅ | `phi-4-4bit` |
| Mistral / Magistral / Ministral | ✅ | hermes | ✅ | `mistral-24b-4bit` |
| MiniMax-M2.5 | ✅ + 🟡 patch (`minimax_m3_sparse_attention`) | minimax | ✅ | `minimax-m2.5-4bit` |
| Kimi-K2 | ✅ | kimi | ✅ | `kimi-k2-4bit` |
| Nemotron | ✅ | hermes | ✅ | `nemotron-30b-4bit` |
| gpt-oss | ✅ | harmony | ✅ | `gpt-oss-20b-mxfp4-q8` |
| Hermes 3 | ✅ | hermes | ✅ | `hermes3-8b-4bit` |
| Granite 4 | ✅ | hermes | — | `granite-4-4bit` |
| Bonsai / Devstral / SmolLM3 / Nanbeige / VibeThinker / Qwopus | ✅ | hermes | varies | `smollm3-3b-4bit` |
| Other mlx-lm arches (119 total) | 🟦 | native `tokenizer.tool_parser` | — | no alias |

### Vision LLMs (mlx-vlm 0.5.0)

| Family | Status | Note |
|--------|--------|------|
| Qwen2-VL / 2.5-VL / 3-VL / 3.5 / 3-Omni | 🟦 | mlx-vlm native |
| Llama 4 / mllama | 🟦 + 🟡 patch | `llama4_attention` |
| Gemma 3 / 3n / 4 | 🟦 | mlx-vlm native |
| GLM-4V / GLM-4V-MoE / GLM-OCR | 🟦 | mlx-vlm native |
| InternVL / Idefics2-3 / Pixtral / Molmo / Phi3-V / Phi4MM | 🟦 | mlx-vlm native |
| MiniCPM-V 4.6 / MiniCPM-o | 🟦 | mlx-vlm native |
| Kimi-K25 / Kimi-VL | 🟦 | mlx-vlm native |
| DeepSeek-VL-V2 / DeepSeekOCR / Florence2 / PaliGemma | 🟦 | mlx-vlm native |
| SAM3 / RFDetr / OCR family | 🟦 | mlx-vlm native |
| Qwen3.6 nested visual | 🟡 patch | `qwen3_6_nested_visual` |

### Specialized modalities

| Family | Status | Engine | Alias example |
|--------|--------|--------|---------------|
| bge-m3 (embedding) | ✅ | Embedding | `bge-m3-4bit` |
| xlm-roberta (reranker/NER) | ✅ | Reranker / NER | — |
| Diffusion-Gemma (text-diffusion) | ✅ | LLM-diffusion | `diffusion-gemma-26b-4bit` |
| UI-TARS (computer-use agent) | ✅ | LLM + `ui_tars` parser | `ui-tars-1.5-7b-4bit` |
| TurboQuant attention | 🟡 patch | quantized-arch accel | — |
| Step3p7 | 🟡 patch | vendored arch | — |

> **Counts**: 81 fusion aliases across ~24 families; mlx-lm 0.31.3 ships
> 119 LLM arches and mlx-vlm 0.5.0 ~60 VLM arches. Aliases = the subset
> we register, document, and run in CI. Upstream-only arches run but are
> not individually verified — add an alias + test to promote one to ✅.
Loading