Files
wehub-resource-sync e9a2f726c9
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / test (3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:29:51 +08:00

212 lines
9.1 KiB
Ruby

class Omlx < Formula
desc "LLM inference server optimized for Apple Silicon"
homepage "https://github.com/jundot/omlx"
url "https://github.com/jundot/omlx/archive/refs/tags/v0.5.1.tar.gz"
sha256 "74eaad96f8e9fa0e65f3ea95534ec1c5e33285248158996d37c69bdfec882b81"
license "Apache-2.0"
head "https://github.com/jundot/omlx.git", branch: "main"
option "with-custom-kernel",
"Build native custom kernels for GLM-5.2, MiniMax M3 and Qwen3.5/3.6 acceleration"
option "with-grammar", "Install xgrammar for structured output (requires torch, ~2GB)"
depends_on "rust" => :build
depends_on arch: :arm64
depends_on :macos
depends_on "python@3.11"
# macOS 27 beta's `strip` corrupts dynamic offsets in Mach-O libraries
# (llvm/llvm-project#203678). Skip Homebrew's post-install clean pass over
# the venv so it never runs `strip` on the compiled dylibs.
on_macos do
skip_clean "libexec" if MacOS.version >= "27"
end
# mlx-audio pins mlx-lm==0.31.1 which conflicts with omlx's git-pinned
# mlx-lm. Fetch source separately so we can patch the pin before install.
resource "mlx-audio" do
url "https://github.com/Blaizzy/mlx-audio.git",
revision: "51753266e0a4f766fd5e6fbc46652224efc23981"
end
# Kokoro's English G2P path uses misaki + spaCy. Bundle the spaCy
# language model so the first TTS request does not download into the
# Homebrew venv at runtime.
resource "en-core-web-sm" do
url "https://github.com/explosion/spacy-models/releases/download/" \
"en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl"
sha256 "1932429db727d4bff3deed6b34cfc05df17794f4a52eeb26cf8928f7c1a0fb85"
end
service do
run [opt_bin/"omlx", "serve"]
keep_alive true
working_dir var
log_path var/"log/omlx.log"
error_log_path var/"log/omlx.log"
environment_variables PATH: std_service_path_env
end
def install
# Create venv with pip so dependency resolution works properly
system "python3.11", "-m", "venv", libexec
# Build native extensions from source with headerpad so Homebrew can
# rewrite Mach-O install names to absolute Cellar/opt paths. Rust/maturin
# extension builds (cohere_melody) need the linker flag via RUSTFLAGS;
# C/C++ extension builds use LDFLAGS.
ENV.append "LDFLAGS", "-Wl,-headerpad_max_install_names"
ENV.append "RUSTFLAGS", "-C link-arg=-Wl,-headerpad_max_install_names"
no_binary = "cohere_melody,pydantic-core,rpds-py,tiktoken"
pip_flags = []
if MacOS.version >= "27"
# macOS 27's dyld requires the LC_SYMTAB string pool to start on an
# 8-byte boundary; prebuilt Rust wheels aligned to 4 bytes fail dlopen
# with "mis-aligned LINKEDIT string pool". Build them from source, and
# keep Cargo/maturin's release stripping off so the beta's broken
# `strip` (llvm/llvm-project#203678) never touches the fresh dylibs.
no_binary += ",tokenizers"
ENV["CARGO_PROFILE_RELEASE_STRIP"] = "false"
ENV["MATURIN_STRIP"] = "false"
# Pip reuses locally built wheels even under --no-binary, so a wheel
# cached before the strip guards existed stays corrupted. Bypass the
# cache entirely.
pip_flags << "--no-cache-dir"
end
# Every pip step must share these flags; a later step without --no-binary
# (e.g. mlx-audio) can clobber a source-built package with a prebuilt
# wheel that fails dlopen on macOS 27 (#2110).
pip_install = [libexec/"bin/pip", "install", *pip_flags, "--no-binary", no_binary]
if build.with?("custom-kernel")
kernel_sources = [
buildpath/"omlx/custom_kernels/glm_moe_dsa/csrc",
buildpath/"omlx/custom_kernels/minimax_m3/csrc",
buildpath/"omlx/custom_kernels/qwen35_prefill/csrc",
]
unless kernel_sources.all?(&:directory?)
odie "--with-custom-kernel requires oMLX custom kernel sources; use --HEAD or a release that includes them"
end
ENV["OMLX_WITH_CUSTOM_KERNEL"] = "1"
# Pin CMake to the venv's Python; its default discovery can pick a
# newer unlinked system Python instead. setup.py forwards CMAKE_ARGS
# to the kernel builds.
ENV.append "CMAKE_ARGS", "-DPython_EXECUTABLE=#{libexec}/bin/python"
end
# Install omlx (with optional grammar extra for structured output)
install_spec = build.with?("grammar") ? "#{buildpath}[grammar]" : buildpath.to_s
system(*pip_install, install_spec)
if build.with?("custom-kernel")
# Run from libexec so buildpath's raw omlx/ source tree doesn't shadow
# the compiled package in the venv's site-packages.
Dir.chdir(libexec) do
system libexec/"bin/python", "-c", <<~PYTHON
from omlx.custom_kernels.glm_moe_dsa import fast as glm_fast
from omlx.custom_kernels.minimax_m3 import fast as minimax_fast
from omlx.custom_kernels.qwen35_prefill import fast as qwen35_fast
assert glm_fast.is_native_available(), glm_fast.import_error()
assert minimax_fast.is_native_available(), minimax_fast.import_error()
assert qwen35_fast.is_native_available(), qwen35_fast.import_error()
PYTHON
end
end
# Install mlx-audio with patched mlx-lm pin to avoid version conflict
resource("mlx-audio").stage do
inreplace "pyproject.toml", '"mlx-lm==0.31.1"', '"mlx-lm>=0.31.1"'
system(*pip_install, ".[all]")
end
# Install the spaCy English model required by misaki for Kokoro TTS.
# Homebrew's cached resource path is hash-prefixed, which pip rejects
# as an invalid wheel filename. Copy it back to the canonical basename.
spacy_model_wheel = buildpath/"en_core_web_sm-3.8.0-py3-none-any.whl"
cp resource("en-core-web-sm").cached_download, spacy_model_wheel
system libexec/"bin/pip", "install", "--no-deps",
spacy_model_wheel
system libexec/"bin/python", "-c",
"import spacy; spacy.load('en_core_web_sm')"
# python-multipart is declared in omlx's [audio] extra, not in mlx-audio
system(*pip_install, "python-multipart>=0.0.5")
bin.install_symlink Dir[libexec/"bin/omlx"]
end
# Patch the macOS arm64 xgrammar wheel so its native binding loads.
# The 0.1.32+ wheel ships libxgrammar_bindings.dylib with
# @rpath/libtvm_ffi.dylib but no LC_RPATH pointing at where tvm_ffi
# installs its native lib, and the dist-info is missing a RECORD
# entry for the dylib so tvm_ffi's manifest-based lookup fails.
# Both manifest as RuntimeError("Cannot find library: ...") at
# `import xgrammar`, which crashes /admin/api/grammar/parsers and
# hides the Reasoning Parser dropdown. Tracking upstream:
# jundot/omlx#1005.
#
# Runs in post_install rather than install because Homebrew's
# post-install "Cleaning" step deletes every dist-info/RECORD file
# in the keg as part of its relocation pass (RECORD hashes become
# invalid once brew rewrites Mach-O install names). Anything we
# write to RECORD inside `def install` is wiped before the user
# sees it.
def post_install
return if build.without?("grammar")
ohai "Patching xgrammar macOS arm64 wheel"
py = libexec/"bin/python"
site = Utils.safe_popen_read(py, "-c",
"import site; print(site.getsitepackages()[0])").chomp
tvmlib = Utils.safe_popen_read(py, "-c",
"import os, tvm_ffi; print(os.path.join(os.path.dirname(tvm_ffi.__file__), 'lib'))").chomp
dylib = "#{site}/xgrammar/libxgrammar_bindings.dylib"
dist_dirs = Dir["#{site}/xgrammar-*.dist-info"]
ohai " site=#{site}"
ohai " tvmlib=#{tvmlib}"
ohai " dylib=#{dylib} (exists? #{File.exist?(dylib)})"
ohai " dist-info=#{dist_dirs.inspect}"
odie "xgrammar dylib not found at #{dylib}" unless File.exist?(dylib)
odie "xgrammar dist-info not found under #{site}" if dist_dirs.empty?
# Patch 1: add tvm_ffi/lib to the dylib's rpath, then re-codesign so
# macOS will load the modified dylib.
rpaths = Utils.safe_popen_read("/usr/bin/otool", "-l", dylib)
if rpaths.include?(tvmlib)
ohai " rpath already points at tvm_ffi/lib"
else
ohai " adding rpath -> #{tvmlib}"
system "/usr/bin/install_name_tool", "-add_rpath", tvmlib, dylib
system "/usr/bin/codesign", "--force", "--sign", "-", dylib
end
# Patch 2: ensure RECORD lists the dylib so tvm_ffi's manifest-based
# lookup finds it. Brew's clean pass already deleted every RECORD by
# the time post_install runs, so we always (re)create one.
record = "#{dist_dirs.first}/RECORD"
if File.exist?(record) && File.read(record).include?("libxgrammar_bindings.dylib")
ohai " RECORD already lists the dylib"
else
ohai " writing dylib entry to #{record}"
File.open(record, "a") { |f| f.puts "xgrammar/libxgrammar_bindings.dylib,," }
end
# Verify the patch took. Failing here is much less confusing than
# the user discovering it later via a 500 from the admin route.
ohai " verifying import xgrammar..."
system py, "-c", "import xgrammar; print('xgrammar import OK')"
end
test do
assert_match version.to_s, shell_output("#{bin}/omlx --version")
system libexec/"bin/python", "-c",
"import spacy; spacy.load('en_core_web_sm')"
end
end