[object Object]

← back to Exo Keepalive

Path B: finish-exo-inference.sh — auto-chain build+load+smoketest once Xcode Metal toolchain is ready

6d9fdda07ad25c9fea894937f69862dc0821f8c4 · 2026-07-06 21:48:38 -0700 · steve

Files touched

Diff

commit 6d9fdda07ad25c9fea894937f69862dc0821f8c4
Author: steve <steve@designerwallcoverings.com>
Date:   Mon Jul 6 21:48:38 2026 -0700

    Path B: finish-exo-inference.sh — auto-chain build+load+smoketest once Xcode Metal toolchain is ready
---
 finish-exo-inference.sh | 45 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/finish-exo-inference.sh b/finish-exo-inference.sh
new file mode 100755
index 0000000..7bf21b0
--- /dev/null
+++ b/finish-exo-inference.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+# finish-exo-inference.sh — run ON .151 AFTER Xcode is installed + selected.
+# Chains: build mlx fork -> restart exo -> load both models -> smoke-test.
+# Everything after the (Steve-only) Apple-ID install + sudo license steps.
+set -uo pipefail
+API="http://localhost:52415"
+say(){ printf '\n=== %s ===\n' "$1"; }
+
+# 0. gate: Metal toolchain present (Steve did the sudo xcode-select + license)
+if ! xcrun -sdk macosx metal --version >/dev/null 2>&1; then
+  echo "!! Metal compiler not found yet. Finish these first (Steve):"
+  echo "   sudo xcode-select -s /Applications/Xcode.app/Contents/Developer"
+  echo "   sudo xcodebuild -license accept"
+  exit 1
+fi
+
+say "1/3 build mlx fork + enable inference"
+bash "$HOME/Projects/exo-keepalive/enable-exo-inference.sh" || exit 1
+
+say "2/3 load models (text workhorse + vision)"
+load_model(){
+  local M="$1"
+  curl -s -m10 "$API/instance/previews?model_id=$M" 2>/dev/null > /tmp/prev.json
+  python3 - "$M" <<'PY'
+import json,sys
+d=json.load(open('/tmp/prev.json'))
+ok=[p for p in d.get('previews',[]) if p.get('error') is None]
+if ok: json.dump({'instance':ok[0]['instance']}, open('/tmp/inst.json','w')); print(f"  {sys.argv[1]}: {len(ok)} viable placement(s) -> creating")
+else: print(f"  {sys.argv[1]}: NO viable placement (not enough RAM?)"); sys.exit(1)
+PY
+  [ -s /tmp/inst.json ] && curl -s -m15 -X POST "$API/instance" -H 'Content-Type: application/json' -d @/tmp/inst.json >/dev/null 2>&1
+}
+# vision first (small, fast), then the text workhorse (bigger download)
+load_model "mlx-community/Qwen3-VL-4B-Instruct-4bit"
+load_model "mlx-community/Qwen3-Next-80B-A3B-Instruct-4bit"
+
+say "3/3 smoke-test (waits for download+load, then generates)"
+for i in $(seq 1 120); do   # up to ~30min for the 80B download
+  out=$(curl -s -m120 -X POST "$API/v1/chat/completions" -H 'Content-Type: application/json' \
+    -d '{"model":"mlx-community/Qwen3-VL-4B-Instruct-4bit","messages":[{"role":"user","content":"Reply with exactly: EXO_OK"}],"max_tokens":300,"stream":false}' 2>/dev/null \
+    | python3 -c "import sys,json;print((json.load(sys.stdin).get('choices',[{}])[0].get('message',{}).get('content') or '').strip())" 2>/dev/null)
+  if echo "$out" | grep -q "EXO_OK"; then echo "  ✅ exo inference LIVE — VL model answered: $out"; break; fi
+  echo "  [$i] loading... (VL says: '${out:0:30}')"; sleep 15
+done
+echo "== done. exo cluster now serves local inference at $API/v1/chat/completions =="

← 90fd7c0 Path B: enable-exo-inference.sh — one-command mlx-fork build  ·  back to Exo Keepalive  ·  auto-save: 2026-07-07T08:56:34 (2 files) — exo.log run-exo.s 85ad721 →