← back to Exo
Add Fast Synch Enabled toggle to macOS app settings (#1852)
645bc2095008b123bbdbdeccd05dfe641219c986 · 2026-04-08 02:04:42 +0100 · Alex Cheema
## Motivation
The exo backend already supports `--fast-synch` / `--no-fast-synch` CLI
flags and the `EXO_FAST_SYNCH` environment variable, but there was no
way to toggle this from the macOS app UI. Users who want fast CPU-to-GPU
synchronization for RDMA with Tensor Parallelism had to use CLI flags.
## Changes
- **ExoProcessController.swift**: Added `fastSynchEnabled`
UserDefaults-backed property and pass `EXO_FAST_SYNCH=on` to the exo
process environment when enabled.
- **SettingsView.swift**: Added a "Performance" section to the Advanced
tab with a "Fast Synch Enabled" toggle, an info icon (ⓘ) tooltip
explaining the feature and trade-offs, and a "Save & Restart" button.
## Why It Works
Follows the exact same pattern as the existing `offlineMode` and
`enableImageModels` settings — UserDefaults persistence, `@Published`
property with `didSet`, environment variable passthrough in
`makeEnvironment()`, and pending state with Save & Restart in the
settings UI. The `EXO_FAST_SYNCH=on` value matches what the Python
backend already reads in `main.py`.
## Test Plan
### Manual Testing
<!-- Hardware: macOS app -->
- Open Settings → Advanced tab → verify "Performance" section with "Fast
Synch Enabled" toggle appears
- Hover the ⓘ icon → verify tooltip explains the feature and GPU lock
trade-off
- Toggle on → click "Save & Restart" → verify process restarts with
`EXO_FAST_SYNCH=on` in env
- Close and reopen Settings → verify the toggle state persists
- Verify "Save & Restart" button is disabled when no changes are pending
### Automated Testing
- Existing settings patterns are well-established; no new automated
tests needed for this UI toggle
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Files touched
M app/EXO/EXO/ExoProcessController.swiftM app/EXO/EXO/Views/SettingsView.swiftM dashboard/package-lock.jsonM src/exo/main.pyM src/exo/worker/runner/bootstrap.py
Diff
commit 645bc2095008b123bbdbdeccd05dfe641219c986
Author: Alex Cheema <41707476+AlexCheema@users.noreply.github.com>
Date: Wed Apr 8 02:04:42 2026 +0100
Add Fast Synch Enabled toggle to macOS app settings (#1852)
## Motivation
The exo backend already supports `--fast-synch` / `--no-fast-synch` CLI
flags and the `EXO_FAST_SYNCH` environment variable, but there was no
way to toggle this from the macOS app UI. Users who want fast CPU-to-GPU
synchronization for RDMA with Tensor Parallelism had to use CLI flags.
## Changes
- **ExoProcessController.swift**: Added `fastSynchEnabled`
UserDefaults-backed property and pass `EXO_FAST_SYNCH=on` to the exo
process environment when enabled.
- **SettingsView.swift**: Added a "Performance" section to the Advanced
tab with a "Fast Synch Enabled" toggle, an info icon (ⓘ) tooltip
explaining the feature and trade-offs, and a "Save & Restart" button.
## Why It Works
Follows the exact same pattern as the existing `offlineMode` and
`enableImageModels` settings — UserDefaults persistence, `@Published`
property with `didSet`, environment variable passthrough in
`makeEnvironment()`, and pending state with Save & Restart in the
settings UI. The `EXO_FAST_SYNCH=on` value matches what the Python
backend already reads in `main.py`.
## Test Plan
### Manual Testing
<!-- Hardware: macOS app -->
- Open Settings → Advanced tab → verify "Performance" section with "Fast
Synch Enabled" toggle appears
- Hover the ⓘ icon → verify tooltip explains the feature and GPU lock
trade-off
- Toggle on → click "Save & Restart" → verify process restarts with
`EXO_FAST_SYNCH=on` in env
- Close and reopen Settings → verify the toggle state persists
- Verify "Save & Restart" button is disabled when no changes are pending
### Automated Testing
- Existing settings patterns are well-established; no new automated
tests needed for this UI toggle
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
app/EXO/EXO/ExoProcessController.swift | 13 +++++++++++++
app/EXO/EXO/Views/SettingsView.swift | 28 ++++++++++++++++++++++++++++
dashboard/package-lock.json | 10 ----------
src/exo/main.py | 4 ++--
src/exo/worker/runner/bootstrap.py | 6 +++---
5 files changed, 46 insertions(+), 15 deletions(-)
diff --git a/app/EXO/EXO/ExoProcessController.swift b/app/EXO/EXO/ExoProcessController.swift
index f978d0e0..d25236ef 100644
--- a/app/EXO/EXO/ExoProcessController.swift
+++ b/app/EXO/EXO/ExoProcessController.swift
@@ -7,6 +7,7 @@ private let hfTokenKey = "EXOHFToken"
private let hfEndpointKey = "EXOHFEndpoint"
private let enableImageModelsKey = "EXOEnableImageModels"
private let offlineModeKey = "EXOOfflineMode"
+private let fastSynchEnabledKey = "EXOFastSynchEnabled"
private let onboardingCompletedKey = "EXOOnboardingCompleted"
@MainActor
@@ -78,6 +79,17 @@ final class ExoProcessController: ObservableObject {
UserDefaults.standard.set(offlineMode, forKey: offlineModeKey)
}
}
+ @Published var fastSynchEnabled: Bool = {
+ if UserDefaults.standard.object(forKey: fastSynchEnabledKey) == nil {
+ return true
+ }
+ return UserDefaults.standard.bool(forKey: fastSynchEnabledKey)
+ }()
+ {
+ didSet {
+ UserDefaults.standard.set(fastSynchEnabled, forKey: fastSynchEnabledKey)
+ }
+ }
/// Fires once when EXO transitions to `.running` for the very first time (fresh install).
@Published private(set) var isFirstLaunchReady = false
@@ -291,6 +303,7 @@ final class ExoProcessController: ObservableObject {
if offlineMode {
environment["EXO_OFFLINE"] = "true"
}
+ environment["EXO_FAST_SYNCH"] = fastSynchEnabled ? "true" : "false"
var paths: [String] = []
if let existing = environment["PATH"], !existing.isEmpty {
diff --git a/app/EXO/EXO/Views/SettingsView.swift b/app/EXO/EXO/Views/SettingsView.swift
index 64deae83..7dfc703b 100644
--- a/app/EXO/EXO/Views/SettingsView.swift
+++ b/app/EXO/EXO/Views/SettingsView.swift
@@ -15,6 +15,7 @@ struct SettingsView: View {
@State private var pendingHFEndpoint: String = ""
@State private var pendingEnableImageModels = false
@State private var pendingOfflineMode = false
+ @State private var pendingFastSynchEnabled = false
@State private var needsRestart = false
@State private var bugReportInFlight = false
@State private var bugReportMessage: String?
@@ -46,6 +47,7 @@ struct SettingsView: View {
pendingHFEndpoint = controller.hfEndpoint
pendingEnableImageModels = controller.enableImageModels
pendingOfflineMode = controller.offlineMode
+ pendingFastSynchEnabled = controller.fastSynchEnabled
needsRestart = false
}
}
@@ -137,6 +139,23 @@ struct SettingsView: View {
private var advancedTab: some View {
Form {
+ Section("Performance") {
+ Toggle("Fast Synch Enabled", isOn: $pendingFastSynchEnabled)
+ Text(
+ "Experimental: enables fast CPU to GPU synchronization. Can sometimes cause a \"GPU lock\" where inference hangs for ~10 seconds before starting. Necessary for low latency with RDMA and Tensor Parallelism."
+ )
+ .font(.caption)
+ .foregroundColor(.secondary)
+
+ HStack {
+ Spacer()
+ Button("Save & Restart") {
+ applyAdvancedSettings()
+ }
+ .disabled(!hasAdvancedChanges)
+ }
+ }
+
Section("Onboarding") {
HStack {
VStack(alignment: .leading) {
@@ -475,6 +494,10 @@ struct SettingsView: View {
pendingEnableImageModels != controller.enableImageModels
}
+ private var hasAdvancedChanges: Bool {
+ pendingFastSynchEnabled != controller.fastSynchEnabled
+ }
+
private func applyGeneralSettings() {
controller.customNamespace = pendingNamespace
controller.hfToken = pendingHFToken
@@ -488,6 +511,11 @@ struct SettingsView: View {
restartIfRunning()
}
+ private func applyAdvancedSettings() {
+ controller.fastSynchEnabled = pendingFastSynchEnabled
+ restartIfRunning()
+ }
+
private func restartIfRunning() {
if controller.status == .running || controller.status == .starting {
controller.restart()
diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json
index cf8ef1dc..341dce91 100644
--- a/dashboard/package-lock.json
+++ b/dashboard/package-lock.json
@@ -1116,7 +1116,6 @@
"integrity": "sha512-oH8tXw7EZnie8FdOWYrF7Yn4IKrqTFHhXvl8YxXxbKwTMcD/5NNCryUSEXRk2ZR4ojnub0P8rNrsVGHXWqIDtA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@sveltejs/acorn-typescript": "^1.0.5",
@@ -1156,7 +1155,6 @@
"integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^4.0.1",
"debug": "^4.4.1",
@@ -1773,7 +1771,6 @@
"integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@@ -1783,7 +1780,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2196,7 +2192,6 @@
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"dev": true,
"license": "ISC",
- "peer": true,
"engines": {
"node": ">=12"
}
@@ -2924,7 +2919,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -2967,7 +2961,6 @@
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -3140,7 +3133,6 @@
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.45.3.tgz",
"integrity": "sha512-ngKXNhNvwPzF43QqEhDOue7TQTrG09em1sd4HBxVF0Wr2gopAmdEWan+rgbdgK4fhBtSOTJO8bYU4chUG7VXZQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -3285,7 +3277,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -3307,7 +3298,6 @@
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
diff --git a/src/exo/main.py b/src/exo/main.py
index 68cd70d4..77dd4e58 100644
--- a/src/exo/main.py
+++ b/src/exo/main.py
@@ -285,10 +285,10 @@ def main():
# Set FAST_SYNCH override env var for runner subprocesses
if args.fast_synch is True:
- os.environ["EXO_FAST_SYNCH"] = "on"
+ os.environ["EXO_FAST_SYNCH"] = "true"
logger.info("FAST_SYNCH forced ON")
elif args.fast_synch is False:
- os.environ["EXO_FAST_SYNCH"] = "off"
+ os.environ["EXO_FAST_SYNCH"] = "false"
logger.info("FAST_SYNCH forced OFF")
node = anyio.run(Node.create, args)
diff --git a/src/exo/worker/runner/bootstrap.py b/src/exo/worker/runner/bootstrap.py
index 644b1a51..675dd302 100644
--- a/src/exo/worker/runner/bootstrap.py
+++ b/src/exo/worker/runner/bootstrap.py
@@ -27,10 +27,10 @@ def entrypoint(
resource.setrlimit(resource.RLIMIT_NOFILE, (min(max(soft, 2048), hard), hard))
fast_synch_override = os.environ.get("EXO_FAST_SYNCH")
- if fast_synch_override != "off":
- os.environ["MLX_METAL_FAST_SYNCH"] = "1"
- else:
+ if fast_synch_override == "false":
os.environ["MLX_METAL_FAST_SYNCH"] = "0"
+ else:
+ os.environ["MLX_METAL_FAST_SYNCH"] = "1"
logger.info(f"Fast synch flag: {os.environ['MLX_METAL_FAST_SYNCH']}")
← 5757c27d Add download utility script (#1855)
·
back to Exo
·
Catch ClosedResourceError when forwarding chunks to client q 62570227 →