← back to Exo
feat: remember last launch settings (model, sharding, instance type) (#1028)
47b8e0ce12e3bd0b417a13d50188d8fc2e9dc73a · 2026-01-05 11:27:14 +0000 · Drifter4242
## Motivation
Saves the last launch settings, so that the next time you run exo it
will default to the same launch settings.
This is just a small quality of life improvement.
## Changes
When you launch it saves the settings to the web browser local storage.
When it fills out the model list, it reads the settings and sets the
default.
I reviewed, tested and edited the code, but some of the code was written
by Claude Opus. I hope that's ok.
## Why It Works
See above
## Test Plan
### Manual Testing
I have two Macbook Studio M3 Ultras, each with 512Gb ram, connected with
Thunderbolt 5. I ran Kimi K2 Thinking with MLX Ring and Tensor Split.
I ran exo multiple times to confirm that the default works.
### Automated Testing
No changes to automated testing.
Files touched
M dashboard/src/routes/+page.svelte
Diff
commit 47b8e0ce12e3bd0b417a13d50188d8fc2e9dc73a
Author: Drifter4242 <davehind@yahoo.co.uk>
Date: Mon Jan 5 11:27:14 2026 +0000
feat: remember last launch settings (model, sharding, instance type) (#1028)
## Motivation
Saves the last launch settings, so that the next time you run exo it
will default to the same launch settings.
This is just a small quality of life improvement.
## Changes
When you launch it saves the settings to the web browser local storage.
When it fills out the model list, it reads the settings and sets the
default.
I reviewed, tested and edited the code, but some of the code was written
by Claude Opus. I hope that's ok.
## Why It Works
See above
## Test Plan
### Manual Testing
I have two Macbook Studio M3 Ultras, each with 512Gb ram, connected with
Thunderbolt 5. I ran Kimi K2 Thinking with MLX Ring and Tensor Split.
I ran exo multiple times to confirm that the default works.
### Automated Testing
No changes to automated testing.
---
dashboard/src/routes/+page.svelte | 67 ++++++++++++++++++++++++++++++++++++---
1 file changed, 63 insertions(+), 4 deletions(-)
diff --git a/dashboard/src/routes/+page.svelte b/dashboard/src/routes/+page.svelte
index 9f75eac8..abc603ff 100644
--- a/dashboard/src/routes/+page.svelte
+++ b/dashboard/src/routes/+page.svelte
@@ -51,6 +51,59 @@ const sidebarVisible = $derived(chatSidebarVisible());
let selectedSharding = $state<'Pipeline' | 'Tensor'>('Pipeline');
type InstanceMeta = 'MlxRing' | 'MlxIbv' | 'MlxJaccl';
+ // Launch defaults persistence
+ const LAUNCH_DEFAULTS_KEY = 'exo-launch-defaults';
+ interface LaunchDefaults {
+ modelId: string | null;
+ sharding: 'Pipeline' | 'Tensor';
+ instanceType: InstanceMeta;
+ minNodes: number;
+ }
+
+ function saveLaunchDefaults(): void {
+ const defaults: LaunchDefaults = {
+ modelId: selectedPreviewModelId(),
+ sharding: selectedSharding,
+ instanceType: selectedInstanceType,
+ minNodes: selectedMinNodes,
+ };
+ try {
+ localStorage.setItem(LAUNCH_DEFAULTS_KEY, JSON.stringify(defaults));
+ } catch (e) {
+ console.warn('Failed to save launch defaults:', e);
+ }
+ }
+
+ function loadLaunchDefaults(): LaunchDefaults | null {
+ try {
+ const stored = localStorage.getItem(LAUNCH_DEFAULTS_KEY);
+ if (!stored) return null;
+ return JSON.parse(stored) as LaunchDefaults;
+ } catch (e) {
+ console.warn('Failed to load launch defaults:', e);
+ return null;
+ }
+ }
+
+ function applyLaunchDefaults(availableModels: Array<{id: string}>, maxNodes: number): void {
+ const defaults = loadLaunchDefaults();
+ if (!defaults) return;
+
+ // Apply sharding and instance type unconditionally
+ selectedSharding = defaults.sharding;
+ selectedInstanceType = defaults.instanceType;
+
+ // Apply minNodes if valid (between 1 and maxNodes)
+ if (defaults.minNodes && defaults.minNodes >= 1 && defaults.minNodes <= maxNodes) {
+ selectedMinNodes = defaults.minNodes;
+ }
+
+ // Only apply model if it exists in the available models
+ if (defaults.modelId && availableModels.some(m => m.id === defaults.modelId)) {
+ selectPreviewModel(defaults.modelId);
+ }
+ }
+
let selectedInstanceType = $state<InstanceMeta>('MlxRing');
let selectedMinNodes = $state<number>(1);
let minNodesInitialized = $state(false);
@@ -298,6 +351,9 @@ function toggleInstanceDownloadDetails(nodeId: string): void {
const data = await response.json();
// API returns { data: [{ id, name }] } format
models = data.data || [];
+ // Restore last launch defaults if available
+ const currentNodeCount = topologyData() ? Object.keys(topologyData()!.nodes).length : 1;
+ applyLaunchDefaults(models, currentNodeCount);
}
} catch (error) {
console.error('Failed to fetch models:', error);
@@ -988,6 +1044,7 @@ function toggleInstanceDownloadDetails(nodeId: string): void {
function handleSliderMouseUp() {
isDraggingSlider = false;
+ saveLaunchDefaults();
}
// Handle touch events for mobile
@@ -1007,6 +1064,7 @@ function toggleInstanceDownloadDetails(nodeId: string): void {
function handleSliderTouchEnd() {
isDraggingSlider = false;
+ saveLaunchDefaults();
}
const nodeCount = $derived(data ? Object.keys(data.nodes).length : 0);
@@ -1464,6 +1522,7 @@ function toggleInstanceDownloadDetails(nodeId: string): void {
onclick={() => {
if (modelCanFit) {
selectPreviewModel(model.id);
+ saveLaunchDefaults();
isModelDropdownOpen = false;
modelDropdownSearch = '';
}
@@ -1497,7 +1556,7 @@ function toggleInstanceDownloadDetails(nodeId: string): void {
<div class="text-xs text-white/70 font-mono mb-2">Sharding:</div>
<div class="flex gap-2">
<button
- onclick={() => selectedSharding = 'Pipeline'}
+ onclick={() => { selectedSharding = 'Pipeline'; saveLaunchDefaults(); }}
class="flex items-center gap-2 py-2 px-4 text-sm font-mono border rounded transition-all duration-200 cursor-pointer {selectedSharding === 'Pipeline' ? 'bg-transparent text-exo-yellow border-exo-yellow' : 'bg-transparent text-white/70 border-exo-medium-gray/50 hover:border-exo-yellow/50'}"
>
<span class="w-4 h-4 rounded-full border-2 flex items-center justify-center {selectedSharding === 'Pipeline' ? 'border-exo-yellow' : 'border-exo-medium-gray'}">
@@ -1508,7 +1567,7 @@ function toggleInstanceDownloadDetails(nodeId: string): void {
Pipeline
</button>
<button
- onclick={() => selectedSharding = 'Tensor'}
+ onclick={() => { selectedSharding = 'Tensor'; saveLaunchDefaults(); }}
class="flex items-center gap-2 py-2 px-4 text-sm font-mono border rounded transition-all duration-200 cursor-pointer {selectedSharding === 'Tensor' ? 'bg-transparent text-exo-yellow border-exo-yellow' : 'bg-transparent text-white/70 border-exo-medium-gray/50 hover:border-exo-yellow/50'}"
>
<span class="w-4 h-4 rounded-full border-2 flex items-center justify-center {selectedSharding === 'Tensor' ? 'border-exo-yellow' : 'border-exo-medium-gray'}">
@@ -1526,7 +1585,7 @@ function toggleInstanceDownloadDetails(nodeId: string): void {
<div class="text-xs text-white/70 font-mono mb-2">Instance Type:</div>
<div class="flex gap-2">
<button
- onclick={() => selectedInstanceType = 'MlxRing'}
+ onclick={() => { selectedInstanceType = 'MlxRing'; saveLaunchDefaults(); }}
class="flex items-center gap-2 py-2 px-4 text-sm font-mono border rounded transition-all duration-200 cursor-pointer {selectedInstanceType === 'MlxRing' ? 'bg-transparent text-exo-yellow border-exo-yellow' : 'bg-transparent text-white/70 border-exo-medium-gray/50 hover:border-exo-yellow/50'}"
>
<span class="w-4 h-4 rounded-full border-2 flex items-center justify-center {selectedInstanceType === 'MlxRing' ? 'border-exo-yellow' : 'border-exo-medium-gray'}">
@@ -1537,7 +1596,7 @@ function toggleInstanceDownloadDetails(nodeId: string): void {
MLX Ring
</button>
<button
- onclick={() => selectedInstanceType = 'MlxIbv'}
+ onclick={() => { selectedInstanceType = 'MlxIbv'; saveLaunchDefaults(); }}
class="flex items-center gap-2 py-2 px-4 text-sm font-mono border rounded transition-all duration-200 cursor-pointer {selectedInstanceType === 'MlxIbv' ? 'bg-transparent text-exo-yellow border-exo-yellow' : 'bg-transparent text-white/70 border-exo-medium-gray/50 hover:border-exo-yellow/50'}"
>
<span class="w-4 h-4 rounded-full border-2 flex items-center justify-center {selectedInstanceType === 'MlxIbv' ? 'border-exo-yellow' : 'border-exo-medium-gray'}">
← 17f9b583 Task Deduplication (#1062)
·
back to Exo
·
[feat] Add an option to disable the worker (#1091) 839b67f3 →