← back to Exo
Ciaran/num sync steps (#1396)
63e9cc4fea7d4fb57ea205bf110c48b1fb439842 · 2026-02-06 15:51:46 +0000 · ciaranbor
## Motivation
Allow users to directly configure num_sync_steps for distributed image
generation instead of deriving it from a factor of total steps.
## Changes
- Added num_sync_steps field to AdvancedImageParams API (range 1-50)
- Changed model configs from num_sync_steps_factor: float to
num_sync_steps: int
- Updated Flux/Qwen configs with direct values (1, 4, 7 respectively)
- Added slider control in dashboard advanced params panel
- Falls back to model default when not specified
## Why It Works
Decouples sync steps from inference steps, giving users direct control
over distributed inference synchronization while preserving sensible
defaults.
## Test Plan
### Manual Testing
- Generate images with various sync step values via dashboard slider
- Verify default behavior when parameter is unset
Files touched
M dashboard/src/lib/components/ImageParamsPanel.svelteM dashboard/src/lib/stores/app.svelte.tsM src/exo/shared/types/api.pyM src/exo/worker/engines/image/config.pyM src/exo/worker/engines/image/distributed_model.pyM src/exo/worker/engines/image/models/flux/config.pyM src/exo/worker/engines/image/models/qwen/config.py
Diff
commit 63e9cc4fea7d4fb57ea205bf110c48b1fb439842
Author: ciaranbor <81697641+ciaranbor@users.noreply.github.com>
Date: Fri Feb 6 15:51:46 2026 +0000
Ciaran/num sync steps (#1396)
## Motivation
Allow users to directly configure num_sync_steps for distributed image
generation instead of deriving it from a factor of total steps.
## Changes
- Added num_sync_steps field to AdvancedImageParams API (range 1-50)
- Changed model configs from num_sync_steps_factor: float to
num_sync_steps: int
- Updated Flux/Qwen configs with direct values (1, 4, 7 respectively)
- Added slider control in dashboard advanced params panel
- Falls back to model default when not specified
## Why It Works
Decouples sync steps from inference steps, giving users direct control
over distributed inference synchronization while preserving sensible
defaults.
## Test Plan
### Manual Testing
- Generate images with various sync step values via dashboard slider
- Verify default behavior when parameter is unset
---
.../src/lib/components/ImageParamsPanel.svelte | 57 +++++++++++++++++++++-
dashboard/src/lib/stores/app.svelte.ts | 38 +++++++--------
src/exo/shared/types/api.py | 1 +
src/exo/worker/engines/image/config.py | 6 +--
src/exo/worker/engines/image/distributed_model.py | 5 +-
src/exo/worker/engines/image/models/flux/config.py | 4 +-
src/exo/worker/engines/image/models/qwen/config.py | 4 +-
7 files changed, 84 insertions(+), 31 deletions(-)
diff --git a/dashboard/src/lib/components/ImageParamsPanel.svelte b/dashboard/src/lib/components/ImageParamsPanel.svelte
index d3a9fbef..5a126f38 100644
--- a/dashboard/src/lib/components/ImageParamsPanel.svelte
+++ b/dashboard/src/lib/components/ImageParamsPanel.svelte
@@ -148,6 +148,15 @@
setImageGenerationParams({ guidance: null });
}
+ function handleNumSyncStepsChange(event: Event) {
+ const value = parseInt((event.target as HTMLInputElement).value, 10);
+ setImageGenerationParams({ numSyncSteps: value });
+ }
+
+ function clearNumSyncSteps() {
+ setImageGenerationParams({ numSyncSteps: null });
+ }
+
function handleReset() {
resetImageGenerationParams();
showAdvanced = false;
@@ -157,7 +166,8 @@
params.seed !== null ||
params.numInferenceSteps !== null ||
params.guidance !== null ||
- (params.negativePrompt !== null && params.negativePrompt.trim() !== ""),
+ (params.negativePrompt !== null && params.negativePrompt.trim() !== "") ||
+ params.numSyncSteps !== null,
);
</script>
@@ -578,7 +588,50 @@
</div>
</div>
- <!-- Row 3: Negative Prompt -->
+ <!-- Row 3: Sync Steps -->
+ <div class="flex items-center gap-1.5">
+ <span
+ class="text-xs text-exo-light-gray uppercase tracking-wider whitespace-nowrap"
+ >SYNC STEPS:</span
+ >
+ <div class="flex items-center gap-2 flex-1 max-w-xs">
+ <input
+ type="range"
+ min="1"
+ max="100"
+ value={params.numSyncSteps ?? 1}
+ oninput={handleNumSyncStepsChange}
+ class="flex-1 h-1 bg-exo-medium-gray/50 rounded appearance-none cursor-pointer accent-exo-yellow"
+ />
+ <span class="text-xs font-mono text-exo-yellow w-8 text-right">
+ {params.numSyncSteps ?? "--"}
+ </span>
+ {#if params.numSyncSteps !== null}
+ <button
+ type="button"
+ onclick={clearNumSyncSteps}
+ class="text-exo-light-gray hover:text-exo-yellow transition-colors"
+ title="Clear"
+ >
+ <svg
+ class="w-3 h-3"
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke="currentColor"
+ >
+ <path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M6 18L18 6M6 6l12 12"
+ />
+ </svg>
+ </button>
+ {/if}
+ </div>
+ </div>
+
+ <!-- Row 4: Negative Prompt -->
<div class="flex flex-col gap-1.5">
<span class="text-xs text-exo-light-gray uppercase tracking-wider"
>NEGATIVE PROMPT:</span
diff --git a/dashboard/src/lib/stores/app.svelte.ts b/dashboard/src/lib/stores/app.svelte.ts
index 6fdb0c7c..ffb7efcc 100644
--- a/dashboard/src/lib/stores/app.svelte.ts
+++ b/dashboard/src/lib/stores/app.svelte.ts
@@ -298,6 +298,7 @@ export interface ImageGenerationParams {
numInferenceSteps: number | null;
guidance: number | null;
negativePrompt: string | null;
+ numSyncSteps: number | null;
// Edit mode params
inputFidelity: "low" | "high";
}
@@ -319,6 +320,7 @@ const DEFAULT_IMAGE_PARAMS: ImageGenerationParams = {
numInferenceSteps: null,
guidance: null,
negativePrompt: null,
+ numSyncSteps: null,
inputFidelity: "low",
};
@@ -2396,7 +2398,9 @@ class AppStore {
params.seed !== null ||
params.numInferenceSteps !== null ||
params.guidance !== null ||
- (params.negativePrompt !== null && params.negativePrompt.trim() !== "");
+ (params.negativePrompt !== null &&
+ params.negativePrompt.trim() !== "") ||
+ params.numSyncSteps !== null;
const requestBody: Record<string, unknown> = {
model,
@@ -2421,6 +2425,9 @@ class AppStore {
params.negativePrompt.trim() !== "" && {
negative_prompt: params.negativePrompt,
}),
+ ...(params.numSyncSteps !== null && {
+ num_sync_steps: params.numSyncSteps,
+ }),
};
}
@@ -2670,29 +2677,19 @@ class AppStore {
formData.append("input_fidelity", params.inputFidelity);
// Advanced params
- if (params.seed !== null) {
- formData.append(
- "advanced_params",
- JSON.stringify({
- seed: params.seed,
- ...(params.numInferenceSteps !== null && {
- num_inference_steps: params.numInferenceSteps,
- }),
- ...(params.guidance !== null && { guidance: params.guidance }),
- ...(params.negativePrompt !== null &&
- params.negativePrompt.trim() !== "" && {
- negative_prompt: params.negativePrompt,
- }),
- }),
- );
- } else if (
+ const hasAdvancedParams =
+ params.seed !== null ||
params.numInferenceSteps !== null ||
params.guidance !== null ||
- (params.negativePrompt !== null && params.negativePrompt.trim() !== "")
- ) {
+ (params.negativePrompt !== null &&
+ params.negativePrompt.trim() !== "") ||
+ params.numSyncSteps !== null;
+
+ if (hasAdvancedParams) {
formData.append(
"advanced_params",
JSON.stringify({
+ ...(params.seed !== null && { seed: params.seed }),
...(params.numInferenceSteps !== null && {
num_inference_steps: params.numInferenceSteps,
}),
@@ -2701,6 +2698,9 @@ class AppStore {
params.negativePrompt.trim() !== "" && {
negative_prompt: params.negativePrompt,
}),
+ ...(params.numSyncSteps !== null && {
+ num_sync_steps: params.numSyncSteps,
+ }),
}),
);
}
diff --git a/src/exo/shared/types/api.py b/src/exo/shared/types/api.py
index 5a7bae1e..97d7041d 100644
--- a/src/exo/shared/types/api.py
+++ b/src/exo/shared/types/api.py
@@ -272,6 +272,7 @@ class AdvancedImageParams(BaseModel):
num_inference_steps: Annotated[int, Field(ge=1, le=100)] | None = None
guidance: Annotated[float, Field(ge=1.0, le=20.0)] | None = None
negative_prompt: str | None = None
+ num_sync_steps: Annotated[int, Field(ge=1, le=100)] | None = None
class ImageGenerationTaskParams(BaseModel):
diff --git a/src/exo/worker/engines/image/config.py b/src/exo/worker/engines/image/config.py
index 69c46ac8..c5d6b613 100644
--- a/src/exo/worker/engines/image/config.py
+++ b/src/exo/worker/engines/image/config.py
@@ -1,5 +1,4 @@
from enum import Enum
-from math import ceil
from pydantic import BaseModel
@@ -23,7 +22,7 @@ class ImageModelConfig(BaseModel):
block_configs: tuple[TransformerBlockConfig, ...]
default_steps: dict[str, int] # {"low": X, "medium": Y, "high": Z}
- num_sync_steps_factor: float # Fraction of steps for sync phase
+ num_sync_steps: int # Number of sync steps for distributed inference
guidance_scale: float | None = None # None or <= 1.0 disables CFG
@@ -45,6 +44,3 @@ class ImageModelConfig(BaseModel):
def get_steps_for_quality(self, quality: str) -> int:
return self.default_steps[quality]
-
- def get_num_sync_steps(self, steps: int) -> int:
- return ceil(steps * self.num_sync_steps_factor)
diff --git a/src/exo/worker/engines/image/distributed_model.py b/src/exo/worker/engines/image/distributed_model.py
index 8c9bd04c..88b60836 100644
--- a/src/exo/worker/engines/image/distributed_model.py
+++ b/src/exo/worker/engines/image/distributed_model.py
@@ -150,7 +150,10 @@ class DistributedImageModel:
guidance=guidance_override if guidance_override is not None else 4.0,
)
- num_sync_steps = self._config.get_num_sync_steps(steps)
+ if advanced_params is not None and advanced_params.num_sync_steps is not None:
+ num_sync_steps = advanced_params.num_sync_steps
+ else:
+ num_sync_steps = self._config.num_sync_steps
for result in self._runner.generate_image(
runtime_config=config,
diff --git a/src/exo/worker/engines/image/models/flux/config.py b/src/exo/worker/engines/image/models/flux/config.py
index 0cf85d74..d37161b0 100644
--- a/src/exo/worker/engines/image/models/flux/config.py
+++ b/src/exo/worker/engines/image/models/flux/config.py
@@ -15,7 +15,7 @@ FLUX_SCHNELL_CONFIG = ImageModelConfig(
),
),
default_steps={"low": 1, "medium": 2, "high": 4},
- num_sync_steps_factor=0.5, # 1 sync step for medium (2 steps)
+ num_sync_steps=1,
)
@@ -30,5 +30,5 @@ FLUX_DEV_CONFIG = ImageModelConfig(
),
),
default_steps={"low": 10, "medium": 25, "high": 50},
- num_sync_steps_factor=0.125, # ~3 sync steps for medium (25 steps)
+ num_sync_steps=4,
)
diff --git a/src/exo/worker/engines/image/models/qwen/config.py b/src/exo/worker/engines/image/models/qwen/config.py
index 4ec2cb35..40a3ee73 100644
--- a/src/exo/worker/engines/image/models/qwen/config.py
+++ b/src/exo/worker/engines/image/models/qwen/config.py
@@ -12,7 +12,7 @@ QWEN_IMAGE_CONFIG = ImageModelConfig(
),
),
default_steps={"low": 10, "medium": 25, "high": 50},
- num_sync_steps_factor=0.25,
+ num_sync_steps=7,
guidance_scale=3.5, # Set to None or < 1.0 to disable CFG
)
@@ -24,6 +24,6 @@ QWEN_IMAGE_EDIT_CONFIG = ImageModelConfig(
),
),
default_steps={"low": 10, "medium": 25, "high": 50},
- num_sync_steps_factor=0.25,
+ num_sync_steps=7,
guidance_scale=3.5,
)
← 9b5cae3d auto bench (#1405)
·
back to Exo
·
More image dimensions (#1395) c8d3154f →