-
Notifications
You must be signed in to change notification settings - Fork 417
Expand file tree
/
Copy pathpyproject.toml
More file actions
733 lines (718 loc) · 35.8 KB
/
Copy pathpyproject.toml
File metadata and controls
733 lines (718 loc) · 35.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "rapid-mlx"
version = "0.14.1"
description = "Rapid-MLX — AI inference for Apple Silicon. Drop-in OpenAI API, up to 3x Ollama's measured throughput."
readme = "README.md"
license = {text = "Apache-2.0"}
requires-python = ">=3.10"
authors = [
{name = "Rapid-MLX contributors"}
]
keywords = ["llm", "mlx", "apple-silicon", "inference", "transformers"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: Apache Software License",
"Operating System :: MacOS",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
dependencies = [
# Core — these are all you need for `rapid-mlx serve <text-model>`
# Floor bump to 0.31.2 (mlx) + 0.31.3 (mlx-lm) for #720 take-2 — see
# tests/test_mllm_cross_thread_stream_contract.py for the empirical
# status of the cross-thread `Stream(gpu, N)` crash on this floor.
# The 0.7.41 ``mx.default_stream`` shim in engine_core.py and
# mllm_batch_generator.py is DELIBERATELY retained: 0.31.2 added
# ``mx.new_thread_local_stream`` / ``ThreadLocalStream`` / ``clear_streams``
# and mlx-lm 0.31.3 switched its module-level ``generation_stream`` to
# ``mx.new_thread_local_stream`` (mlx_lm/generate.py:226 in 0.31.3), but
# repro on 0.31.2 still crashes when arrays produced on thread T1 (with
# default_stream / new_stream / new_thread_local_stream) are materialised
# on T2 via np.array(arr). The shim adopts the worker's process-wide
# default — the only path that round-trips cleanly across threads.
# Upper caps are coherence-gated (#1248): mlx / mlx-lm are floor-only
# pinned but move fast, so a bare ``pip install rapid-mlx`` silently resolves
# to a NEWER, untested minor. The Qwen3.6
# garbage incident (#1234) was caused by a heuristic change INSIDE
# ``mlx_lm.models.qwen3_5.sanitize`` under an existing model — an upstream
# minor we never validated. MLX 0.32.1 improves long-prompt prefill by about
# 21-22% on Qwen hybrid/GDN models and passed the #1247 coherence campaign
# recorded in the accompanying performance report; retain the next-minor
# cap so another runtime change cannot arrive unswept. Raising either bound
# is a gated coherence event — see scripts/check_mlx_bound_move.py.
"mlx>=0.32.1,<0.33",
"mlx-lm>=0.31.3,<0.32", # 0.31.1+ required for presence_penalty/frequency_penalty kwargs on make_logits_processors (#512); 0.31.3 added thread-local generation_stream but cross-thread eval is still broken (see shim sites). Upper cap <0.32 (#1248).
# mlx-vlm is opt-in via [vision] extras (saves ~322 MB for text-only users)
#
# transformers 5.13.0 tightened
# ``AutoTokenizer.register`` to dereference ``key.__module__`` while mlx-lm
# still registers tokenizers by *string* class name (mlx_lm/
# tokenizer_utils.py:505) — so under 5.13 even a bare ``import mlx_lm``
# (and therefore every serve / gemma-4 / DiffusionGemma launch) raises
# ``AttributeError: 'str' object has no attribute '__module__'`` at import
# time. The regression is specific to 5.13.0; 5.13.1 fixed string-key
# registration, and the 5.15.x line completed the dependency-coherence
# sweep with mlx-lm 0.31.3. Keep the bad release excluded explicitly and
# retain a next-minor cap so a new Transformers runtime cannot arrive
# unswept. The Desktop's reduced sidecar runtime remains independently
# pinned and validated.
#
# HY3 vendor note (2026-07-09): the 0.11.0 Hy3 vendor drop
# (``vllm_mlx/models/hy_v3.py``) was validated end-to-end against
# ``transformers==5.7.0`` — that release is treated as the *canonical
# reference version* for the vendored model_type. The public floor
# stays at 5.0.0 so we don't force a downgrade / resolver conflict
# on users whose environments already sit at 5.8-5.12 for another
# dependency — the vendored HY3 tokenizer path in
# ``vllm_mlx/utils/tokenizer.py`` bypasses ``AutoTokenizer`` entirely
# (via ``_VENDORED_MODEL_TYPES``), so the transformers minor is not
# in the HY3 hot path. If a future transformers release above 5.7
# introduces an HY3-specific regression, we tighten the floor with a
# dedicated bump PR + Phase-0 re-audit rather than pinning here.
"transformers>=5.0.0,!=5.13.0,<5.16",
"tokenizers>=0.19.0",
"huggingface-hub>=0.23.0",
"numpy>=1.24.0",
"tqdm>=4.66.0",
# pillow is opt-in via [vision] extras — only the MLLM paths (mllm.py,
# benchmark.py image mode) touch PIL, and mlx-vlm requires it anyway.
"pyyaml>=6.0",
# Writing TOML, so ``rapid-mlx agents codex --setup`` can deep-merge into
# an existing ~/.codex/config.toml instead of replacing it (#1532).
# Reading is stdlib ``tomllib`` on our supported floor; only the write
# side needs a package, and this is the companion to the ``tomli`` the
# [test] extra already pins. Pure Python, no transitive deps.
"tomli-w>=1.0.0",
# The read side is stdlib ``tomllib`` from 3.11; 3.10 is still a supported
# floor and has no ``tomllib``, so the merge path needs the backport there.
# It was a [test]-only pin before, which would have made `codex --setup`
# abort with ModuleNotFoundError on 3.10 — and only when a config already
# existed, so every fresh-write test would still have passed.
'tomli>=2.0.1; python_version < "3.11"',
"requests>=2.28.0",
"rich>=13.8.0",
"tabulate>=0.9.0",
"psutil>=5.9.0",
"fastapi>=0.100.0",
"uvicorn>=0.23.0",
"mcp>=1.9.3", # ClientSessionGroup: official multi-server lifecycle for chat
"jsonschema>=4.18.0",
# Direct import used by the packaged atomic contract registry. jsonschema
# also depends on it today, but keeping the direct dependency explicit
# prevents a resolver change from breaking `rapid-mlx models --json`.
"referencing>=0.28.4",
# argcomplete — shell tab completion for the `rapid-mlx` CLI. Tiny
# pure-Python package (~50 KB) wired into argparse via the magic
# `# PYTHON_ARGCOMPLETE_OK` marker in cli.py and a per-arg
# `.completer = alias_completer` for model positionals. Required
# (not optional) so `rapid-mlx chat gemma-4-<TAB>` works on every
# install without an extras step. Activation in the user's shell is
# one-time: `eval "$(register-python-argcomplete rapid-mlx)"` in
# ~/.zshrc, or `activate-global-python-argcomplete` system-wide.
# The Homebrew formula handles activation automatically.
#
# >=3.6 is required: a recent Python 3.12 micro release added a 4th
# positional `intermixed` argument to argparse._parse_known_args.
# argcomplete <3.6 subclasses that hook with only 3 positional args,
# so the very first Tab raises TypeError mid-handshake and the shell
# silently shows nothing. 3.6.0 ships the variadic signature fix.
"argcomplete>=3.6",
# WebSocket client for ``rapid-mlx share`` — connects to the
# rapidserver Worker as the reverse-tunnel transport. Pure Python,
# ~200 KB. Replaces the prior frpc Go binary download.
"websockets>=12.0",
# openai-harmony — official harmony-protocol streaming parser. Used
# by ``HarmonyStreamingRouter`` (output_router_harmony.py) to route
# gpt-oss tool calls correctly (issue #513 / cluster #444 #455
# #468 #480). Same library vLLM and SGLang delegate to for gpt-oss
# tool calling. Soft-imported at runtime; the legacy custom state
# machine remains the fallback if the dep is unavailable.
"openai-harmony>=0.0.8",
# llguidance — grammar-constrained decoding engine (native MLX Metal
# mask kernel via ``llguidance.mlx``). PROMOTED FROM THE [guided] EXTRA
# TO CORE in 0.10.15 (#558 fix-slot follow-up to PR-5). Rationale: #558
# PR-5 made grammar-constrained tool-calling DEFAULT-ON in 0.10.14, but a
# fresh-venv dogfood proved ``pip install rapid-mlx`` does NOT pull
# llguidance (it lived only in [guided]) — so default-on silently
# degraded to free-form for naive users ("on in name, off in reality":
# ``routes/chat.py::_maybe_build_tool_grammar_processor`` returns None
# when ``get_lltokenizer`` finds no llguidance, falling back to
# free-form-then-parse). Promoting to core makes default-on genuinely
# constrained out-of-the-box. Wheel/site-packages cost is a few MB
# (well within G12's +25 MB budget). The defensive ``HAS_LLGUIDANCE`` /
# ``is_guided_available()`` guards in api/guided.py + api/tool_grammar.py
# are RETAINED as defense-in-depth (a broken/incompatible install still
# degrades gracefully rather than crashing).
"llguidance>=1.7.6",
]
[project.optional-dependencies]
# Vision/multimodal models (Gemma 4, Qwen-VL, etc.) — adds ~322 MB
# Required for any model with vision input. Text-only models work without this.
# 0.5.0+ also unlocks DFlash speculative decoding (see [dflash] extras).
vision = [
# Keep the optional runtime identical to the signed Desktop sidecar.
# A floating 0.6.x range backtracks to 0.6.3 under older Transformers
# constraints and can also advance CLI users beyond the Desktop's validated
# lane. Updating this exact pin is a gated coherence event (#1248).
"mlx-vlm==0.6.17",
"opencv-python>=4.8.0",
"torch>=2.3.0",
"torchvision>=0.18.0",
# vllm_mlx imports PIL directly on vision paths (mllm.py, benchmark.py
# image mode); mlx-vlm requires it too — listed for explicitness.
"pillow>=10.0.0",
]
# DFlash speculative decoding for Qwen3.5/3.6 dense 8-bit models
# (issue #264). Adds ~1-4 GB at runtime (drafter weights) and depends on
# mlx-vlm's spec-decode runtime. Text-only — does NOT pull torch/cv2 like
# the vision extras do. Only enable if you serve a DFlash-eligible alias
# (e.g. qwen3.5-27b-8bit) with --speculative-config '{"method":"dflash"}'.
dflash = [
"mlx-vlm==0.6.17",
]
# MTP (Multi-Token Prediction) speculative decoding — Gemma 4 external
# assistant path via ``--speculative-config '{"method":"mtp","model":"<path>"}'``.
# assistant-drafter loader in
# ``vllm_mlx/spec_decode/mtp/gemma4_inject.py`` transitively pulls
# ``mlx_lm.utils`` → ``transformers`` which reaches for PIL when a
# checkpoint contains an image-processor preprocessor_config.json (the
# case for the assistant drafters we've dogfooded so far). Without PIL
# on PYTHONPATH, boot fails with ``ModuleNotFoundError: No module named
# 'PIL'`` at the ``dispatch_mtp_inject`` call site, BEFORE the eligibility
# banner prints — misleading users into thinking their sidecar path is
# wrong. Text-only; does NOT pull torch/cv2 like [vision].
#
# Codex round-I NIT (0.9.13 PR-A): do NOT re-pin ``mlx-vlm`` here.
# The only place a Gemma 4 MTP boot touches ``mlx_vlm`` at all is
# during target model construction in
# ``vllm_mlx/models/gemma4_text.py``, which surfaces its own
# actionable ImportError with a size-tiered install hint (16 MB vs
# +450 MB). Duplicating that pin under ``[mtp]`` doubled the install
# surface of a text-only sidecar path just to satisfy a transitive
# PIL import that ``pillow`` already covers directly. Operators who
# want Gemma 4 targets install ``rapid-mlx[mtp,vision]`` (or the
# ``pip install --no-deps mlx-vlm>=0.6.1`` sliver flagged by
# gemma4_text.py) — the ``[mtp]`` extra stays narrow to just the
# MTP subsystem's own direct dependencies.
mtp = [
"pillow>=10.0.0",
]
# Embedding endpoint
embeddings = [
"mlx-embeddings>=0.1.0",
]
# HF → Cloudflare R2 mirror tooling (scripts/mirror_to_r2.py).
# Off the hot path — only maintainers who seed models.rapidmlx.com need
# this. Users NEVER install [mirror]; the client-side download path
# (vllm_mlx/_mirror.py) uses stdlib urllib and does NOT depend on boto3.
mirror = [
"boto3>=1.34.0",
]
# Gradio chat UI
chat = [
"gradio>=4.0.0",
"pytz>=2024.1",
]
# All extras — union of vision + chat + embeddings + audio.
# Expanded directly to avoid a self-dependency that breaks
# pip install .[all] and editable installs.
all = [
# vision
"mlx-vlm==0.6.17",
"opencv-python>=4.8.0",
"torch>=2.3.0",
"torchvision>=0.18.0",
"pillow>=10.0.0",
# chat
"gradio>=4.0.0",
"pytz>=2024.1",
# embeddings
"mlx-embeddings>=0.1.0",
# dflash (text-only path covered by mlx-vlm above; listed for clarity)
# audio
"mlx-audio>=0.2.9,<0.4.4",
"f5-tts-mlx==0.2.6",
"sounddevice>=0.4.0",
"soundfile>=0.12.0",
"scipy>=1.10.0",
"numba>=0.57.0",
"tiktoken>=0.5.0",
"misaki[zh,ja]>=0.5.0",
"spacy>=3.7.0",
"num2words>=0.5.0",
"loguru>=0.7.0",
"espeakng-loader>=0.2.0",
"phonemizer-fork>=3.3.0",
"cn2an>=0.5.0",
]
# Test runtime — the *minimum* deps `pytest tests/` needs to import the
# suite and collect every test without a plugin-missing error. Kept
# narrow on purpose so:
# 1. `pr_validate` (scripts/pr_validate/_test_env.py) can install JUST
# this when its env check finds plugins missing, without dragging
# in the linter/typer toolchain.
# 2. Contributors who only want to run tests don't have to install the
# full dev toolbox.
# The canonical source of truth: if pytest needs it to collect/run a
# test, it goes here, not in `dev`.
# Closes #185 — pr_validate's targeted_tests + full_unit gates have
# been crashing across multiple fix waves because pytest-asyncio
# (required by `asyncio_mode = auto` in pytest.ini) keeps falling out
# of the host Python after `pip install --no-deps --force-reinstall .`
# in orchestrated runs. The systematic fix is to publish the test deps
# as their own extras so pr_validate can re-install them from the
# canonical source, instead of every script hand-maintaining a list.
test = [
"pytest>=7.0.0",
# Community protocol tests use jsonschema's external Registry API. Keep
# this explicit so a partial/no-deps test install fails instead of skipping
# the complete contract suite.
"referencing>=0.28.4",
# pytest-asyncio is REQUIRED — pytest.ini sets `asyncio_mode = auto`,
# which means every `async def test_*` is collected as an asyncio
# test. Without the plugin, those tests fail at collection with
# "async def functions are not natively supported".
"pytest-asyncio>=0.21.0",
# Parse the /metrics output through the official Prometheus parser
# to catch format regressions. NOT used at runtime — the route
# hand-rolls the exposition format to avoid a runtime dep.
"prometheus_client>=0.16.0",
# Parse pyproject.toml in the L-07 vision-extra lock-in tests
# (tests/test_vision_extra_install.py). ``tomllib`` is stdlib on
# Python ≥3.11; the environment marker pins ``tomli`` only on
# Python 3.10 so the L-07 suite always runs against the documented
# supported floor (codex round-2 BLOCKING — without this, CI on 3.10
# would module-skip the entire lock-in suite and let a future
# refactor silently drop the ``[vision]`` extra).
'tomli>=2.0.1; python_version < "3.11"',
# Full-unit imports / runs optional-surface tests too. Keep these in
# [test] so pr_validate can rebuild a venv that actually runs
# ``pytest tests/`` without hand-installed local extras.
"aiohttp>=3.9.0",
"pillow>=10.0.0",
"mlx-vlm==0.6.17; platform_system == 'Darwin'",
"mlx-audio>=0.2.9,<0.4.4; platform_system == 'Darwin'",
# Powers the property-based invariant suite in tests/property/ — encodes
# numeric/validation invariants over the whole input space (e.g. the
# #1208-class quantized-KV round-trip bounds) that example tests can't.
"hypothesis>=6.100.0",
# Powers pr_validate's ADVISORY diff_coverage step (measure-only, never
# gates). pytest-cov instruments the unit suite; diff-cover scopes the
# coverage.xml to the PR's changed lines → patch-coverage %. Declared in
# the extras so pr_validate can rebuild a venv that runs the advisory
# measurement. See scripts/pr_validate/steps/diff_coverage.py.
# diff-cover is major-pinned: _parse_diff_cover() reads its human-readable
# footer, so a new major could silently stop baseline collection — bump the
# ceiling deliberately, re-validating the parser, when moving to 9.x (codex
# #1220 r15).
"pytest-cov>=4.0.0",
"diff-cover>=8.0.0,<9.0.0",
]
# CI declared dependency sets — replaces the ad hoc `pip install <list>` lines
# that used to live inline in `.github/workflows/ci.yml`. Each workflow lane's
# runtime surface is versioned and reviewed here instead of buried in a YAML
# one-liner that supply-chain review can't scope. Follows the same declaration
# style as `vision`/`dflash`/`mtp`.
#
# Install mode differs per lane because `ci-linux` must stay no-MLX:
# * Apple lanes (`ci-apple`, a superset of `ci-linux`) install `-e .[<extra>]`
# — MLX is legitimate there (it IS the engine).
# * The Linux `test-matrix` lane installs `-e . --no-deps` (so the base
# package's mlx / mlx-lm deps never land on Linux) plus the ci-linux test
# set from `config/requirements-ci-linux.txt`, which is a SYNCED MIRROR of
# this extra (a drift test in tests/test_ci_apple_coverage_union.py fails
# closed if they fall out of step).
#
# `ci-linux` = the exact dependency set the Linux `test-matrix` lane needs to
# discover and run the complete no-MLX unit suite with coverage. It mirrors the
# pure-Python/core runtime surface while deliberately excluding mlx/mlx-lm,
# then adds test-only packages required by automatically discovered modules.
ci-linux = [
# Test infra — floors mirrored from `test`.
"pytest>=7.0.0",
"pytest-asyncio>=0.21.0",
"pytest-cov>=4.0.0",
"hypothesis>=6.100.0",
"prometheus_client>=0.16.0",
# App runtime surface the Linux (no-MLX) lane imports at module load.
# Pin the floors identical to the core `dependencies` block so the
# no-MLX lane resolves against the same minimum the shipped engine is
# validated on — a bare, unpinned name here could resolve a newer minor
# and silently diverge from what macOS is shipping (the drift test in
# tests/test_ci_apple_coverage_union.py fails closed on any gap).
"pydantic",
"fastapi>=0.100.0",
"jsonschema>=4.18.0",
"referencing>=0.28.4",
"httpx",
"psutil>=5.9.0",
"transformers>=5.0.0,!=5.13.0,<5.16",
"tokenizers>=0.19.0",
"huggingface-hub>=0.23.0",
"numpy>=1.24.0",
"tqdm>=4.66.0",
"requests>=2.28.0",
"pyyaml>=6.0",
"tomli-w>=1.0.0",
'tomli>=2.0.1; python_version < "3.11"',
"rich>=13.8.0",
"tabulate>=0.9.0",
"python-multipart",
"uvicorn>=0.23.0",
"mcp>=1.9.3",
"argcomplete>=3.6",
"websockets>=12.0",
"openai-harmony>=0.0.8",
"llguidance>=1.7.6",
"jinja2",
"aiohttp>=3.9.0",
"pillow>=10.0.0",
]
# `ci-apple` = the pytest + coverage set the `test-apple-silicon` lane installs
# on top of `[vision]`. Same test-infra floors as `[ci-linux]`; the lane's MLX
# runtime comes from `[vision]` + core deps.
ci-apple = [
"pytest>=7.0.0",
"pytest-asyncio>=0.21.0",
"pytest-cov>=4.0.0",
]
dev = [
# Test runtime (must mirror the `test` extras above; a unit test in
# tests/test_pr_validate_test_env.py asserts every `test` dep is
# also in `dev` so a future drift can't silently break the
# "install dev, run pytest" workflow contributors have used since
# day one).
"pytest>=7.0.0",
"referencing>=0.28.4",
"pytest-asyncio>=0.21.0",
"prometheus_client>=0.16.0",
'tomli>=2.0.1; python_version < "3.11"',
"aiohttp>=3.9.0",
"pillow>=10.0.0",
"mlx-vlm==0.6.17; platform_system == 'Darwin'",
"mlx-audio>=0.2.9,<0.4.4; platform_system == 'Darwin'",
# Mirror of the [test] entry — keep `dev` a strict superset of `test`
# (enforced by tests/test_pr_validate_test_env.py).
"hypothesis>=6.100.0",
# Mirror of the [test] entries (dev ⊇ test). Powers the advisory
# diff_coverage step — see scripts/pr_validate/steps/diff_coverage.py.
# diff-cover major-pinned (parser couples to its footer) — see [test] note.
"pytest-cov>=4.0.0",
"diff-cover>=8.0.0,<9.0.0",
# Linters / typer — NOT in the `test` extras because pr_validate
# only needs to *run* the tests; lint is its own pipeline step
# (ruff) and runs from the host environment.
"ruff>=0.1.0",
"mypy>=1.0.0",
]
# Guided decoding with llguidance for structured JSON output.
# llguidance drives constrained decoding via its native MLX Metal mask
# kernel (llguidance.mlx); it interprets JSON Schema natively ($defs/$ref/
# anyOf/enum/numeric-bounds/additionalProperties). Replaced outlines[mlxlm]
# in 0.10 — see vllm_mlx/api/guided.py.
#
# NOTE (0.10.15, #558 fix-slot): llguidance was PROMOTED TO CORE
# ``[project].dependencies`` above so default-on grammar-constrained
# tool-calling (#558 PR-5) actually works out-of-the-box. This extra is
# RETAINED (still pins llguidance explicitly) purely for backward compat so
# ``pip install 'rapid-mlx[guided]'`` — the historical install path printed
# in guided.py's degrade warning and referenced across docs — keeps
# resolving. It is now effectively a no-op superset of core. Follows the
# codebase's established duplicate-pin convention (mlx-vlm is likewise
# repeated across [vision]/[dflash]/[all]/[test]/[dev]).
guided = [
"llguidance>=1.7.6",
]
# Audio dependencies for TTS/STT (mlx-audio)
audio = [
# R7-H3 (Bo 0.8.8 dogfood): pin BELOW 0.4.4. The 0.4.4 release
# regressed ``mlx_audio.tts.models.kokoro.istftnet.SineGen`` so the
# internal interpolation step produces an off-by-one length mismatch
# versus the noise tensor — every Kokoro request ends with
# ``[broadcast_shapes] Shapes (1,36600,1) and (1,36900,9) cannot be
# broadcast`` deep inside the istftnet generator. The rapid-mlx
# route's catch-all collapsed that into the opaque ``No audio
# generated`` 500 (the chunk loop swallowed the upstream raise).
# 0.4.3 (and earlier) does NOT have the regression — the same
# request returns a 73 KB WAV. Cap the upper bound until upstream
# ships a fix so every fresh ``pip install rapid-mlx[audio]``
# works out of the box.
"mlx-audio>=0.2.9,<0.4.4",
# F5-TTS (pure-MLX, no torch): EN+ZH multilingual + zero-shot voice cloning.
# Fills the Chinese expressive/cloneable TTS gap. Standalone package (not an
# mlx_audio family) — wired directly in audio/tts.py's `f5` branch.
"f5-tts-mlx==0.2.6",
"sounddevice>=0.4.0",
"soundfile>=0.12.0",
"scipy>=1.10.0",
"numba>=0.57.0",
"tiktoken>=0.5.0",
"misaki[zh,ja]>=0.5.0", # Chinese (zh) and Japanese (ja) support
"spacy>=3.7.0",
"num2words>=0.5.0",
"loguru>=0.7.0",
# R6-H1 (Eva 0.8.7 dogfood): Kokoro TTS imports ``misaki.espeak`` which
# calls ``EspeakWrapper.set_data_path(...)`` (the legacy 3.2-era API).
# Two things need to line up for that call to succeed on a fresh
# ``pip install rapid-mlx[audio]``:
#
# 1. ``espeakng-loader`` provides the bundled libespeak-ng binary
# and ``espeak-ng-data/`` directory misaki passes to
# ``set_data_path``. Without it, the import crashes inside
# ``misaki/espeak.py`` with ``ModuleNotFoundError`` before any
# TTS request reaches the engine.
# 2. ``phonemizer-fork`` (NOT vanilla ``phonemizer``) is the only
# package that still exposes ``EspeakWrapper.set_data_path``.
# Vanilla ``phonemizer>=3.3.0`` removed it in favour of a
# property-only ``data_path`` accessor — so the legacy API
# misaki uses raises ``AttributeError: type object
# 'EspeakWrapper' has no attribute 'set_data_path'`` and the
# Kokoro TTS route 500s on first request.
#
# Pin ``phonemizer-fork`` BEFORE the vanilla ``phonemizer`` line and
# drop the vanilla pin — pip installs the first one wins and they
# claim the same import name, so leaving both in produces a
# non-deterministic resolution depending on resolver order.
# ``test_audio_extras_lockin.py`` asserts both packages are
# importable and that ``EspeakWrapper.set_data_path`` exists so we
# catch a future drift back to vanilla phonemizer in CI.
"espeakng-loader>=0.2.0",
"phonemizer-fork>=3.3.0",
# Additional multilingual dependencies
"cn2an>=0.5.0", # Chinese number conversion
]
# Audio dependencies shipped inside the macOS desktop sidecar. The desktop
# exposes transcription plus Qwen3 preset-voice speech; it does not expose
# F5 cloning, Kokoro's language-specific G2P stack, or voice design. Keeping
# those families in the full [audio] extra avoids making the public engine
# less capable while preventing the signed app from absorbing llvmlite,
# spaCy, espeak, and language dictionaries it cannot drive from its UI.
audio-desktop = [
"mlx-audio>=0.2.9,<0.4.4",
# vllm_mlx.audio.tts encodes the in-memory result with libsndfile.
"soundfile>=0.12.0",
]
video = [
# MLX-native LTX-2.3 T2V/I2V with synchronized audio. 0.1.36 is the
# first verified release with the split-format LTX-2.3 VAE fixes. The
# upstream runtime requires Python >=3.11, while Rapid-MLX core continues
# to support Python 3.10. Keep every dependency behind the same marker so
# universal lock generation remains satisfiable for the declared core
# Python range; the video startup preflight gives 3.10 users an explicit
# upgrade diagnostic.
"mlx-video-with-audio==0.1.36; platform_system == 'Darwin' and python_version >= '3.11'",
"mlx-arsenal>=0.10.1; platform_system == 'Darwin' and python_version >= '3.11'",
"imageio[ffmpeg]>=2.34.0; platform_system == 'Darwin' and python_version >= '3.11'",
"pillow>=10.0.0; platform_system == 'Darwin' and python_version >= '3.11'",
"sentencepiece>=0.2.0; platform_system == 'Darwin' and python_version >= '3.11'",
]
image = [
# MLX-native text-to-image / image-edit (FLUX.1-schnell, Qwen-Image,
# Qwen-Image-Edit) via mflux. mflux 0.19.0 is the first release compatible
# with MLX 0.32.x; the 0.18.x line pins ``mlx<0.32`` and therefore cannot
# coexist with the coherence-validated core runtime. Keep a minor cap so
# image-lane API changes remain deliberate. mflux requires Python >=3.11
# (Rapid-MLX core still supports 3.10, so the image lane preflight gives
# 3.10 users an explicit upgrade diagnostic). Model-weight licenses are
# independent of this runtime and documented per family.
"mflux>=0.19.0,<0.20; platform_system == 'Darwin' and python_version >= '3.11'",
# HiDream-O1 reuses the exact Qwen3-VL runtime already validated and
# bundled by Desktop. Keep this pin coherent with [vision]/the sidecar.
"mlx-vlm==0.6.17; platform_system == 'Darwin' and python_version >= '3.11'",
# The vendored SDXL backend needs Pillow for PNG encoding. Keep this direct
# because SDXL and SD3.5 also work on Rapid's Python 3.10 floor without
# mflux. SD3.5's reviewed local T5 tokenizer additionally needs
# sentencepiece on that same floor.
"pillow>=10.0.0; platform_system == 'Darwin'",
"sentencepiece>=0.2.0; platform_system == 'Darwin'",
]
[project.urls]
Homepage = "https://github.com/raullenchai/Rapid-MLX"
Documentation = "https://github.com/raullenchai/Rapid-MLX#readme"
Repository = "https://github.com/raullenchai/Rapid-MLX"
[project.scripts]
rapid-mlx = "vllm_mlx.cli:cli_entrypoint"
# First-class short command for accessibility and reduced typing (#1279).
rmlx = "vllm_mlx.cli:cli_entrypoint"
rapid-mlx-chat = "vllm_mlx.gradio_app:main"
rapid-mlx-bench = "vllm_mlx.benchmark:main"
# Deprecated pre-rename aliases — kept so existing users' scripts/muscle
# memory keep working. Prefer the rapid-mlx* commands above.
vllm-mlx = "vllm_mlx.cli:main"
vllm-mlx-chat = "vllm_mlx.gradio_app:main"
vllm-mlx-bench = "vllm_mlx.benchmark:main"
[tool.setuptools.package-data]
vllm_mlx = [
"aliases.json",
"model_recommendations.json",
"audio/aliases.json",
"catalog/schemas/*.json",
# Download-size manifest (hf_path -> footprint bytes) powering the Size
# column in `rapid-mlx models` and the size line in `rapid-mlx info`.
# Regenerate with scripts/gen_model_sizes.py when aliases change.
"model_sizes.json",
# Canonical Google Gemma 4 chat templates used to repair stale converted
# checkpoints without a network fetch at serve time.
"templates/*.jinja",
"agents/profiles/*.yaml",
"kernels/*.metal",
# Integration tests bundled inside the package so `rapid-mlx agents
# <name> --test` works on pip/brew installs (the source layout keeps
# them at tests/integrations/, mirrored here via symlinks). Without
# this, the test runner shows a confusing path-error skip.
"_integration_tests/*.py",
"_integration_tests/*.sh",
# Vendored Stability AI MLX Stable Audio 3 attribution — the MIT license
# text + provenance NOTICE/README must travel with the redistributed code.
"audio/sa3/LICENSE",
"audio/sa3/NOTICE",
"audio/sa3/README.md",
# Vendored HiDream-O1 MLX adapter attribution must ship in wheels and the
# Desktop sidecar alongside the redistributed MIT source.
"image/hidream_runtime/LICENSE",
"image/hidream_runtime/NOTICE",
# Vendored SDXL MLX runtime attribution must ship beside the CC0 source.
"image/sdxl_runtime/LICENSE",
"image/sdxl_runtime/NOTICE",
# Vendored Bonsai low-bit numerical core attribution must ship with every
# wheel and Desktop sidecar that contains the adapted MIT source.
"image/bonsai_runtime/LICENSE",
"image/bonsai_runtime/NOTICE",
# Vendored SD3.5 MLX runtime attribution must ship beside the MIT source.
"image/sd35_runtime/LICENSE",
"image/sd35_runtime/NOTICE",
# DeepSeek V4.1 native target + DSpark implementations retain separate
# upstream licenses; ship both texts and the provenance notice.
"models/deepseek_v41_native/LICENSE",
"models/deepseek_v41_native/LICENSE-DSPARK",
"models/deepseek_v41_native/NOTICE",
]
videox_fun_mlx = ["LICENSE", "NOTICE"]
[tool.setuptools.packages.find]
where = ["."]
include = ["vllm_mlx*", "videox_fun_mlx*"]
[tool.ruff]
line-length = 88
extend-exclude = [
# Vendored CogVideoX-Fun MLX runtime. Keep iso-upstream.
"videox_fun_mlx",
]
[tool.ruff.format]
# Vendored upstream files — keep formatting identical to the source PR
# so future syncs produce a clean diff.
exclude = [
# Ruff 0.16+ formats Python fenced blocks inside Markdown. Documentation
# examples intentionally optimize for readability and are not Python source;
# the repository-wide Python gate should not mechanically rewrite them.
"*.md",
"vllm_mlx/models/deepseek_v4*.py",
"vllm_mlx/models/gemma4_vendored/*.py",
"vllm_mlx/models/hy_v3.py",
# Vendored Stability AI MLX Stable Audio 3 (models + scripts). Keep
# formatting identical to the upstream source so future syncs stay clean.
"vllm_mlx/audio/sa3/**/*.py",
# Vendored Bonsai low-bit FLUX.2 numerical core. The Rapid-owned adapter
# in runtime.py remains under the normal formatter.
"vllm_mlx/image/bonsai_runtime/_vendor/**/*.py",
# Vendored CogVideoX-Fun MLX runtime. Keep iso-upstream.
"videox_fun_mlx/**/*.py",
]
[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "B", "SIM"]
ignore = [
"E402", # module-level import not at top
"E501", # line too long
"E731", # lambda assignment
"F811", # redefined unused
"F841", # unused variable
"B004", # unreliable callable check
"B007", # unused loop variable
"B008", # function call in default arg
"B011", # assert false
"B017", # assert raises exception
"B905", # zip without strict
"N801", # class name casing
"N806", # variable in function casing
"SIM103", # needless bool
"SIM108", # ternary instead of if-else
"SIM110", # reimplemented builtin
"SIM222", # expr or true
"UP028", # yield in for loop
"SIM102", # collapsible if
"SIM105", # suppressible exception
"SIM115", # open file with context handler
"B904", # raise without from inside except
]
[tool.ruff.lint.per-file-ignores]
# Vendored from ml-explore/mlx-lm PR #1192 (Blaizzy). Keep as-is so
# upstream syncs remain a clean diff. Drop this once mlx-lm 0.32+
# ships native deepseek_v4 support and we delete the vendored copy.
"vllm_mlx/models/deepseek_v4*.py" = ["UP", "B", "SIM", "N", "F", "I", "E"]
# Vendored from Blaizzy/mlx-vlm 0.6.3 for the Gemma 4 text-only
# fresh-install regression fix (#1017, closes 0.10.0 dead-end). Keep
# lint quiet on the copy so upstream diffs stay clean. Sync policy
# and rationale in vllm_mlx/models/gemma4_vendored/__init__.py.
"vllm_mlx/models/gemma4_vendored/*.py" = ["UP", "B", "SIM", "N", "F", "I", "E"]
# Vendored from ml-explore/mlx-lm PR #1211 (kernelpool, add-hy3-preview,
# b7635e9c) for Tencent Hunyuan 3 (295B/21B active MoE) as part of the
# 0.11.0 vendor drop. PR is open with zero maintainer reviews since
# 2026-04-27. Keep lint quiet on the copy so upstream sync diffs stay
# clean. Sync policy in vllm_mlx/models/hy_v3.py module docstring.
"vllm_mlx/models/hy_v3.py" = ["UP", "B", "SIM", "N", "F", "I", "E"]
# Vendored Stability AI MLX Stable Audio 3 (models + scripts, ~680K source).
# Keep lint quiet on the copy so upstream sync diffs stay clean. The engine
# entrypoint (vllm_mlx/audio/music.py) is NOT vendored and is linted normally.
"vllm_mlx/audio/sa3/**/*.py" = ["UP", "B", "SIM", "N", "F", "I", "E", "W"]
# Adapted from the pinned upstream HiDream-O1 MLX runtime. Keep source-shape
# lint exceptions local so the Rapid-owned engine integration stays strict.
"vllm_mlx/image/hidream_runtime/*.py" = ["UP", "B", "SIM", "N", "F", "I", "E", "W"]
# Vendored SDXL implementation. Rapid-owned adapter/runtime.py remains linted;
# upstream-shaped model, converter, scheduler, and pipeline files stay syncable.
"vllm_mlx/image/sdxl_runtime/{caching,configuration,hub,modeling,quantization,utils}.py" = ["UP", "B", "SIM", "N", "F", "I", "E", "W"]
"vllm_mlx/image/sdxl_runtime/{converters,models,pipelines,schedulers}/**/*.py" = ["UP", "B", "SIM", "N", "F", "I", "E", "W"]
# Adapted verbatim from the pinned low-bit runtime. Keep exceptions confined
# to _vendor; the checkpoint validation and product adapter stay strict.
"vllm_mlx/image/bonsai_runtime/_vendor/*.py" = ["UP", "B", "SIM", "N", "F", "I", "E", "W"]
# Pinned DiffusionKit numerical core. Rapid-owned adapter/runtime.py stays
# linted; only the source-shaped vendor directory receives exceptions.
"vllm_mlx/image/sd35_runtime/_vendor/**/*.py" = ["UP", "B", "SIM", "N", "F", "I", "E", "W"]
# Tensor-shape parameter names (B, H, N, D) follow ML literature
# convention; N803 lowercase rule is incompatible.
"scripts/bench_attention.py" = ["N803"]
# Pytest parametrize convention: the Pydantic *class* is passed as
# ``Model`` so the test body reads naturally (``Model(model="x", ...)``);
# renaming would lose readability across many tests.
"tests/test_param_validation_r5e.py" = ["N803"]
"tests/test_param_validation_r7b.py" = ["N803"]
# Chain-of-K MTP uses ``K`` as chain length (Ollama speculate.go, Google
# mlx-vlm draft_block); N803 lowercase rule incompatible with the
# speculative-decode literature convention.
"vllm_mlx/spec_decode/mtp/generator.py" = ["N803"]
[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
ignore_missing_imports = true
[[tool.mypy.overrides]]
# Exact third-party numerical core; Rapid-owned runtime.py remains under the
# repository-wide shrink-only error budget.
module = "vllm_mlx.image.bonsai_runtime._vendor.*"
ignore_errors = true
[[tool.mypy.overrides]]
# Pinned third-party numerical core; keep the Rapid-owned SD3.5 adapter under
# the repository-wide shrink-only error budget.
module = "vllm_mlx.image.sd35_runtime._vendor.*"
ignore_errors = true
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
asyncio_mode = "auto"