Skip to content

Add MLX (Apple GPU) to the AI install menu - #385

Open
joshuaswarren wants to merge 4 commits into
omacom:quattrofrom
joshuaswarren:add-mlx-omarchy
Open

Add MLX (Apple GPU) to the AI install menu#385
joshuaswarren wants to merge 4 commits into
omacom:quattrofrom
joshuaswarren:add-mlx-omarchy

Conversation

@joshuaswarren

Copy link
Copy Markdown

Adds an Install > AI > MLX (Apple GPU) entry and bin/omarchy-install-ai-mlx.

mlx-omarchy is Apple's MLX array framework running on the Apple GPU under Linux through Mesa's Honeykrisp Vulkan driver, with a chat demo (mlx-omarchy-demo, also registered in the launcher as "MLX Chat (Apple GPU)"). Demo video and install walkthrough are in that repo's README.

What the install script does:

  • runs the upstream installer (curl -fsSL .../install.sh | bash), which installs the verified release wheel into a private venv under ~/.local/share/mlx-omarchy, adds two launchers to ~/.local/bin, one .desktop entry, and calls omarchy-menu refresh;
  • installs lapack and blas through omarchy-pkg-add;
  • touches nothing else: no Mesa replacement, no Hyprland or Omarchy config edits; bash install.sh --uninstall removes it all;
  • skips with a message on non-aarch64.

Verified on an M1 MacBook Air running Omarchy 4.0.1rc2 with the stock asahi-alarm Mesa 26.1.7 (out-of-box install, demo, launcher entry). The M3 is not supported yet: on the current Omarchy Mac kernel no Apple GPU is exposed to Vulkan there, and the installer's smoke test reports that instead of falling back to the CPU.

Menu row follows the existing install.ai.* entries (disabled when mlx-omarchy-demo is present; action runs in the floating presentation terminal).

omarchy-install-ai-mlx runs the mlx-omarchy installer, which puts Apple's
MLX with the Honeykrisp Vulkan backend and a chat demo into a private venv
under ~/.local/share/mlx-omarchy. It installs lapack and blas through
omarchy-pkg-add and touches nothing else: no Mesa replacement, no
Hyprland or Omarchy config changes. Verified on the M1; skips on other
architectures.
@malik-na

malik-na commented Sep 10, 2026

Copy link
Copy Markdown
Member

Updated after testing 53d5470e from a clean checkout on an M1, Mesa 26.1.8 / Python 3.14, without our earlier local fixes.

What passed

  • Install into a clean user prefix, actual menu removal, and menu reinstall all exited 0. GPU numerical checks and offline multi-turn chat passed before/after reinstall. Qwen2.5-0.5B-Instruct-4bit generated 128 tokens at 77.2–78.1 tok/s.
  • Removal cleared the app and preserved the model cache; reinstall worked. pip check and OpenBLAS linkage were clean. OpenBLAS was already installed from the previous test: this retest verifies the updated dependency declaration and runtime, not a new missing-package transaction.
  • The installer pin/checksum, M1 gate, removal entry, and direct installer exit-status capture are now present. All 266 shell test files and 231 runnable Mac checks passed with CI's pinned package fixture. Environment-guarded cases, including the root-only Btrfs rehearsal, were skipped. All four GitHub checks are now green; the earlier hosted package-pin failure is no longer present.

Two failure paths still need attention

  1. The outer terminal wrapper loses the error. Your installer now correctly returns the upstream failure status. However, bin/omarchy-launch-floating-terminal-with-presentation:11 then calls omarchy-show-done for every status except 130 and does not return the original status. In a real desktop terminal, an intentionally invalid MLX demo option exited 2, but the window showed green “Done” and the wrapper returned 0. This tested the shared presentation path, not a naturally failing install. An independent PTY test also turned 37 into 0 and cancellation 130 into 0. This is a pre-existing shared Omarchy bug, not a regression introduced by your PR. Fix it in a focused shared-wrapper change: capture the status immediately, display success/failure appropriately, and preserve it through terminal completion, keeping existing no-argument callers compatible. Add tests for success, failure and cancellation.

  2. Rollback can remove a pre-existing installation. bin/omarchy-install-ai-mlx:55 runs upstream --uninstall after any installer failure. In a disposable HOME, we created an existing default-prefix marker and forced the Python prerequisite check to fail before package work; cleanup deleted that marker. No real installation was harmed. The menu's installed check reduces exposure, but direct reruns or incomplete installs can still reach this path. Safest small fix: refuse installation before making changes if any app artifacts already exist, with an explicit remove/reinstall instruction. If replacement is supported, preserve the old installation and roll back only this attempt's changes. Also report cleanup failure rather than unconditionally saying nothing remains. Add tests for early failure with existing artifacts, partial fresh-install failure, and failed cleanup.

The normal M1 workflow works now. I'd address those failure contracts, rerun the focused tests and M1 lifecycle, then validate the candidate Omarchy/settings package pair for both fresh and existing installs. Package-pair delivery and physical non-M1 behavior remain untested; desktop tests supplied disk-backed temporary storage. No migration gap was demonstrated by this retest.

Thanks, Joshua—the dependency fix and pinned install/remove path made a real difference on the M1.

joshuaswarren added a commit to joshuaswarren/mlx-omarchy that referenced this pull request Sep 10, 2026
Reported on omacom/omarchy-mac#385: a fresh install failed on
libopenblas.so.0. lapack and blas alone do not provide it.
Four fixes from @malik-na's review, tested on the four paths they touch.

openblas: the wheel resolves libopenblas.so.0 at import, and lapack plus
blas do not provide it. Added upstream in the installer (mlx-omarchy
286ef72), which this entry now pins.

Exit status: the installer's failure was swallowed. `if ! cmd; then
status=$?` records the negation's status, which is always 0, so a failed
install printed "failed (exit 0)" and exited 0. The status is captured
directly now, the error output is kept, a partial install is torn down
with the installer's own --uninstall, and the real code propagates.

Pinned installer: the entry fetched install.sh from a moving branch.
It now fetches one commit and refuses to run it unless the SHA256
matches. The installer verifies the wheel against the release
SHA256SUMS as before, so both hops are checked.

M1 gate: aarch64 admitted every Apple machine, but the wheel is built
for t8103 only. The entry uses omarchy-hw-apple plus a device-tree
check and names the SoC it found when it declines.

Also adds remove.ai.mlx and omarchy-remove-ai-mlx, pinned to the same
installer commit so install and uninstall cannot drift.
@joshuaswarren

Copy link
Copy Markdown
Author

Thanks, this was a good catch on all four. Fixed in dbd15c5, and one of them was worse than reported.

openblas. Added upstream in the installer rather than here, since that is where the package list lives: joshuaswarren/mlx-omarchy@286ef72. This entry pins that commit, so the fix arrives with the pin.

False success. You were right that the terminal said Done after a failure, and the cause is nastier than a missing check. My first attempt at the fix still got it wrong:

if ! bash "$tmp/install.sh"; then
  status=$?   # always 0: this is the negation's status, not the installer's

That reproduced the same bug in a subtler form, reporting "failed (exit 0)" and still exiting 0. The status is now captured directly from the installer. On failure the error output is kept, the partial install is torn down with the installer's own --uninstall, and the real exit code propagates so the menu cannot claim success.

Pin and checksum. The entry fetched install.sh from main. It now fetches one pinned commit and refuses to run it unless the SHA256 matches. The installer already verifies the wheel against the release SHA256SUMS, so both hops are checked now.

M1 gate. aarch64 admitted every Apple machine. It now uses omarchy-hw-apple plus a device-tree check for apple,t8103, and names the SoC it found when it declines, for example Skipping: mlx-omarchy currently supports the M1 (t8103) only. This machine reports: apple,j313 apple,t8112.

Also added remove.ai.mlx and omarchy-remove-ai-mlx, pinned to the same installer commit so install and uninstall cannot drift.

Verified: shellcheck clean on both scripts, tests/all passes, and I exercised the four paths directly — non-Apple skip, M2 skip with the SoC named, tampered checksum refused with exit 1, and a failed install reporting exit 1 after cleanup.

One thing that is not mine to fix: the install.sh on Arch Linux ARM check fails on a package pin unrelated to this change. The artifact's final-package-sources.log says omarchy/hyprtoolkit installed=0.5.4-6 expected=0.5.4-5.1, and the MLX part of that run logged PASS: fresh restricted-source transaction. It looks like it would fail the same way on any PR against quattro right now.

Package-pair deployment is still untested, as you noted. Happy to look at that next if it should block this.

malik-na and others added 2 commits September 10, 2026 18:46
…back

Two failure contracts from @malik-na's retest of 53d5470.

1. The shared presentation wrapper reported every outcome as success. It
   called omarchy-show-done unconditionally and ended on that command's
   status, so a menu entry exiting 2 showed a green "Done!" and handed its
   caller a 0; cancellation became 0 too. The status is now captured on the
   line after the command runs, passed to omarchy-show-done, and used as the
   script's exit status. omarchy-show-done takes an optional status and shows
   a red "Failed (exit N)" for anything non-zero; a missing argument still
   means success, so omarchy-pkg-install, omarchy-pkg-aur-install and
   omarchy-pkg-remove are unaffected. This was a pre-existing bug in shared
   code, not a regression from the MLX entry.

2. omarchy-install-ai-mlx ran the upstream --uninstall after any failure,
   which deletes every artifact unconditionally, so a failed install over a
   working one removed the working one. It now refuses before touching
   anything, or the network, if any artifact already exists, and names
   omarchy-remove-ai-mlx as the way forward. Because that guard means only
   this attempt's files can be present, the cleanup message can be honest:
   a cleanup that fails now says what may remain instead of asserting a
   clean machine.

Tests: tests/test-presentation-exit-status.sh covers propagation of 0, 1, 2
and 37, the reported status, cancellation, the no-argument default, and the
on-terminal messages over a pty. tests/test-ai-mlx-install-guards.sh covers
each of the four artifacts separately, proves the refusal happens before the
fetch and leaves the existing files in place, and exercises a real failing
install and a genuinely failing cleanup against the pinned installer.

Both suites were checked against the bugs they describe: reverting either fix
makes them fail. test/shell is 28 of 266 failing before and after this
change, an identical set, all environmental on a non-Omarchy host.
@joshuaswarren

Copy link
Copy Markdown
Author

Both fixed in 9243282.

1. The wrapper. You were right about the cause, and right that it is not mine. It is in the path of every menu entry I added, so here it is. That one line had two bugs:

# before
presentation_script="omarchy-show-logo; $cmd; if (( \$? != 130 )); then omarchy-show-done; fi"

omarchy-show-done got no argument, so it could only say "Done!". And the script ended on the if, so its status was show-done's 0. The error was displayed, then thrown away.

# after
presentation_script="omarchy-show-logo; $cmd; status=\$?; if (( status != 130 )); then omarchy-show-done \"\$status\"; fi; exit \$status"

omarchy-show-done now takes an optional status. Anything non-zero shows a red Failed (exit N). A missing argument still means success, so omarchy-pkg-install, omarchy-pkg-aur-install and omarchy-pkg-remove behave exactly as before. Cancellation still shows no message, but returns 130 rather than 0.

One thing I did not fix, so it is not overstated. This makes the presentation script exit with the real status. Whether that status then survives setsid uwsm-app -- xdg-terminal-exec out to the wrapper's own caller, I did not verify: I have no Omarchy desktop session on hand. The tests cover the script the wrapper builds, which is where the status was lost.

2. Rollback. Your reproduction is exactly right, and the fix is the ordering you suggested. The entry enumerates the four artifacts that upstream --uninstall deletes. If any exists, it refuses before touching anything, or the network, and points at omarchy-remove-ai-mlx. That guard means only this attempt's files can be present, so the cleanup message no longer has to bluff. A cleanup that fails now says what may remain, instead of "Nothing from mlx-omarchy is left installed".

Tests, for the three cases you named:

  • tests/test-presentation-exit-status.sh: propagation of 0, 1, 2 and 37. That show-done receives 37 rather than nothing. Cancellation returning 130 with no message. The no-argument default. And the real messages over a pty, including a non-numeric status treated as failure rather than success.
  • tests/test-ai-mlx-install-guards.sh: each of the four artifacts separately, asserting that the refusal happens before the fetch and that the pre-existing file survives. A real failing install against the pinned installer, with the status propagated. And a genuinely failing cleanup, arranged by making the directory unwritable, asserting that it reports remnants and does not also claim a clean machine.

I checked both suites against the bugs rather than watching them pass. Revert the wrapper line and the first fails on "a command exiting 1 left the wrapper returning 0". Remove the guard and the second fails on the refusal assertion, having reached the fetch.

test/shell is 28 of 266 failing before and after, an identical set, all environmental on a non-Omarchy host. tests/all passes 15 of 15. One run showed 29: that was preinstalls-test.sh being flaky. It passes in isolation and did not recur.

Package-pair delivery and non-M1 hardware are still untested by me, as you said.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants