Skip to content

Latest commit

 

History

History
189 lines (127 loc) · 11.2 KB

File metadata and controls

189 lines (127 loc) · 11.2 KB

Contributing to Rapid-MLX

Thanks for your interest! Here's how to get started.

Development Setup

# Clone and install in dev mode
git clone https://github.com/raullenchai/Rapid-MLX.git
cd Rapid-MLX
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
pip install pytest ruff        # dev tools for testing and linting

# Start a dev server
rapid-mlx serve qwen3.5-4b-4bit --port 8000

Requirements: Python 3.11+, macOS with Apple Silicon (M1/M2/M3/M4).

Running Tests

# Run all unit tests (no model needed)
python3 -m pytest tests/ -x -q

# Run a specific test file
python3 -m pytest tests/test_tool_calling.py -v

# Lint and format
ruff check .
ruff format --check .

Most tests run without a model. Tests in tests/test_event_loop.py require a running server.

Testing install-time patches

An import-time monkeypatch can pass unit tests while never running in rapid-mlx serve if its installer is wired to the wrong module. For every install-time patch:

  • install it from a module on the real production load path;
  • add a subprocess test that imports that production module before importing the patch's is_installed probe; and
  • assert the installer state and its marker on the patched upstream object.

Use the existing test_install_fires_on_real_serve_import_path tests in tests/test_deepseek_v32_indexer_gate.py and tests/test_qwen3_5_norm_shift.py as the template. The subprocess is required: directly importing the patch in the pytest worker can install it as a side effect and mask a broken production wiring path.

Pull Request Workflow

  1. Fork the repo and create a branch: feat/, fix/, docs/, refactor/
  2. Make your changes with tests if applicable
  3. Run ruff check and ruff format before committing
  4. Self-validate your PR (see below) — saves a round trip with maintainers
  5. Open a PR against main with a clear description, filling in all required sections of the PR template (necessity, AI assistance disclosure, test plan)

Necessity & AI assistance — what we ask, why

The PR template asks two questions up front: why is this needed? and was AI used? These aren't gatekeeping for the sake of it.

  • Necessity — Rapid-MLX auto-publishes to PyPI + Homebrew on every version-bump merge. Drive-by refactors and "increases coverage" PRs cost real review time and add real blast-radius risk for zero user value. PRs whose only justification is "looks cleaner" / "future-proofs" / "good practice" may be closed. What unlocks merge: a concrete user-visible reason ("fixes #123", "restores N% TPS", "patches CVE"), OR concrete repository maintenance value (typo / broken link / docs clarification, alias / metadata bookkeeping, deleting genuinely-dead code, CI/tooling fixes that reduce maintainer toil). The carveout is intentional — typo PRs are welcome, "polish for polish's sake" isn't.

  • AI assistance disclosure — AI-authored code is welcome (we ship a lot of it ourselves). What we ask is honesty about the role and the verification: which files were AI-touched, what the AI did (wrote / reviewed / suggested), and how you confirmed the output is correct. We don't ask for prompt transcripts. "Fully human" and "Claude wrote tests, I wrote the impl and ran the unit suite" are both fine; silence is treated more cautiously than disclosure. The standard: you should be able to explain the intent, risk, and behavior of every non-generated change in your PR on demand. For generated / boilerplate sections (scaffold, lockfile, framework hooks), identify them and describe how you verified them.

The full maintainer-side gauntlet — what happens to your PR after you open it — is documented in docs/development/pr_merge_sop.md.

Self-Validating Your PR

Before opening (or after pushing fixes to) your PR, run our validation pipeline against it. The same script is what maintainers run before merging — running it yourself catches the easy stuff before review and signals you've done your homework.

python3 -m scripts.pr_validate.pr_validate <PR#>

The script grades your PR through 11 steps and prints a strict markdown scorecard. Exit code 0 = MERGE-SAFE, exit code 1 = at least one step failed.

step what it does when
fetch pulls your PR + diff, classifies blast radius always
test_plan_check fails on unchecked - [ ] items in your PR body's ## Test plan always
cl_description_quality title + body hygiene (empty body, bad title, no rationale) always
supply_chain flags new deps, install hooks, eval/exec/shell=True, hardcoded URLs always
test_env_check verifies the test interpreter can import required pytest plugins (trusted-pins auto-install) always
review_vocabulary rejects reviewer-severity directives embedded in added diff lines when a diff is available
codex_review adversarial code review via codex exec (skips if the codex CLI is missing / not logged in) unless PR_VALIDATE_NO_CODEX=1
lint ruff check + ruff format --check when diff has .py
targeted_tests runs tests touching the files you changed; negative-control filters pre-existing flakes when diff has .py
full_unit full pytest suite minus integrations medium/high blast
stress_e2e_bench boots a server, runs stress + agent integrations + bench vs baseline high blast (engine/scheduler/memory_cache)

You don't need every step to pass for a clean PR, but the more green checks you have, the faster review goes. In particular:

  • lint and targeted_tests are non-negotiable — run these locally even without the full pipeline.
  • supply_chain warnings mean a maintainer will read your changes carefully (especially if you touched setup.py, .github/workflows/, Makefile, or added a new dep). That's not a problem — just be ready to explain the why.
  • stress_e2e_bench requires Apple Silicon + enough RAM to load a small model (≥6GB free). If you don't have the hardware, opt out with PR_VALIDATE_NO_STRESS=1 — maintainers will run it for you on merge.
  • codex_review needs the codex CLI logged in via ChatGPT (~/.codex/auth.json) — no API key involved. It skips automatically if codex isn't installed; opt out explicitly with PR_VALIDATE_NO_CODEX=1. (The legacy PR_VALIDATE_NO_DEEPSEEK=1 from the retired DeepSeek review step still works as a deprecated alias.) Maintainers will run it for you.
# Quick local check (no codex review, no stress) — covers the "did I break anything obvious" case in <1 minute for most PRs:
PR_VALIDATE_NO_CODEX=1 PR_VALIDATE_NO_STRESS=1 \
    python3 -m scripts.pr_validate.pr_validate <PR#>

Full step list, gating logic, and how to add steps: scripts/pr_validate/README.md — the canonical pipeline reference; if this table and that README disagree, the README wins.

What if my PR fails on a pre-existing main bug?

targeted_tests already handles this — it re-runs failures on your PR's base commit and reclassifies "fails on main too" as pre-existing (not a regression). For full_unit you'll currently see the failure surfaced; mention it in the PR comment ("test_X is failing on main too — see issue #123") and a maintainer will confirm.

What if pr_validate itself misbehaves?

It's still new. File an issue with [pr_validate] in the title and the artifacts under /tmp/pr_validate/pr-<N>/ attached.

Ways to Contribute

🟢 Easy — No model download needed

  • Add a model alias — Add a short name to vllm_mlx/aliases.json so users can rapid-mlx serve <alias> instead of typing a full HuggingFace path. See open model-support issues.

  • Fix a good first issue — Check the good first issue label.

🟡 Medium — Needs a model + some testing

  • Test a model and report results — Download a model, run benchmarks, report what works. Use the "Model Support Request" issue template.

  • Add parser auto-detection — Add a regex pattern to vllm_mlx/model_auto_config.py so a new model family gets the right tool/reasoning parser automatically.

  • Classify a model into a SuffixDecoding tier — After adding a ModelConfig entry, run python3.12 scripts/bench_suffix_decoding_integrated.py --model <id> (10-20 min). Paste the resulting suffix_decoding_tier= and suffix_bench_speedup= into the entry. Reference the bench output in your PR. See docs/suffix_decoding_eligibility.md.

  • Verify client integrations — Test Rapid-MLX with your favorite AI tool (Cursor, Continue, Aider, LangChain, etc.) and report results.

🔴 Advanced

  • Write a new tool call parser — Add support for a new tool call format in vllm_mlx/tool_parsers/.
  • Performance optimization — Profiling, kernel improvements, caching strategies.
  • BatchedEngine / continuous batching — Multi-user serving improvements.

How to Add a Model Alias

The easiest contribution — no model download needed!

File: vllm_mlx/aliases.json

{
  "my-model-7b": "mlx-community/My-Model-7B-Instruct-4bit"
}

That's it. Find the MLX model on HuggingFace mlx-community and add the mapping. Convention: <family>-<size> in lowercase (e.g., qwen3.5-9b-4bit, gemma-4-26b-4bit).

How to Add Parser Auto-Detection

When users serve a model without --tool-call-parser, Rapid-MLX auto-detects the right parser from the model name.

File: vllm_mlx/model_auto_config.py

# Add your pattern (order matters — more specific first):
(re.compile(r"my-model", re.IGNORECASE), ModelConfig(
    tool_call_parser="hermes",    # most common format
    reasoning_parser=None,        # set if model has thinking tags
)),

Common tool parsers: hermes, llama, deepseek, gemma4, glm47, minimax, kimi. Common reasoning parsers: qwen3, deepseek_r1, gemma4, minimax.

How to figure out the right parser: Check the model's chat template for tool call format. Most models use Hermes-style <tool_call> tags. If unsure, try hermes first.

Code Style

  • We use ruff for linting and formatting
  • Type hints are encouraged but not required
  • Keep changes focused — one feature/fix per PR

Releasing

The release pipeline is fully automated from a single commit on main. Push a commit with subject chore: bump version to X.Y.Z (matching the new pyproject.toml version) and the rest happens on its own: tag → GitHub Release → PyPI → Homebrew formula PR.

If your PR adds a model alias, capability profile, or CLI flag, do not bump pyproject.toml — ship the change with no version bump. The version-check.yml workflow instead blocks a stray version change in any non-bump PR; the release is cut later in a dedicated chore: bump version to X.Y.Z PR that batches the accumulated changes. This batch-then-cut SOP is what prevents a stale rapid-mlx models list (and the silently-skipped releases an inline bump would cause).

Full details, escape hatches, and rationale: docs/development/releasing.md.