[object Object]

← back to Exo

feat: show ETA on prefill progress bar (#1557)

bddad7e79cb505f895f2fa0d8e21892f65b6ac7e · 2026-02-20 04:37:56 -0800 · Alex Cheema

## Summary
- Show estimated time remaining during prefill (prompt processing phase)
- Track prefill start time via performance.now() and extrapolate from
observed token throughput
- Display ~Xs remaining or ~Xm Ys remaining next to the percentage on
the progress bar
- Wait 200ms before showing ETA to ensure a stable sample window

## Changes
**PrefillProgressBar.svelte**: Add etaText derived computation that
calculates remaining time from (remainingTokens / tokensPerMs). Renders
in a new flex row below the progress bar alongside the percentage.

**app.svelte.ts**: Add startedAt: number field to PrefillProgress
interface. Set on first prefill_progress SSE event, preserved across
subsequent updates.

## Test plan
- [ ] Start inference with a long prompt (10k+ tokens) on a multi-node
cluster
- [ ] Verify the progress bar shows ~Xs remaining after ~200ms of
prefill
- [ ] Verify the ETA decreases as prefill progresses
- [ ] Verify short prefills (<200ms) dont flash a briefly-visible ETA
- [ ] Verify ETA disappears when prefill completes and token generation
begins

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: rltakashige <rl.takashige@gmail.com>

Files touched

Diff

commit bddad7e79cb505f895f2fa0d8e21892f65b6ac7e
Author: Alex Cheema <41707476+AlexCheema@users.noreply.github.com>
Date:   Fri Feb 20 04:37:56 2026 -0800

    feat: show ETA on prefill progress bar (#1557)
    
    ## Summary
    - Show estimated time remaining during prefill (prompt processing phase)
    - Track prefill start time via performance.now() and extrapolate from
    observed token throughput
    - Display ~Xs remaining or ~Xm Ys remaining next to the percentage on
    the progress bar
    - Wait 200ms before showing ETA to ensure a stable sample window
    
    ## Changes
    **PrefillProgressBar.svelte**: Add etaText derived computation that
    calculates remaining time from (remainingTokens / tokensPerMs). Renders
    in a new flex row below the progress bar alongside the percentage.
    
    **app.svelte.ts**: Add startedAt: number field to PrefillProgress
    interface. Set on first prefill_progress SSE event, preserved across
    subsequent updates.
    
    ## Test plan
    - [ ] Start inference with a long prompt (10k+ tokens) on a multi-node
    cluster
    - [ ] Verify the progress bar shows ~Xs remaining after ~200ms of
    prefill
    - [ ] Verify the ETA decreases as prefill progresses
    - [ ] Verify short prefills (<200ms) dont flash a briefly-visible ETA
    - [ ] Verify ETA disappears when prefill completes and token generation
    begins
    
    Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
    Co-authored-by: rltakashige <rl.takashige@gmail.com>
---
 .../src/lib/components/PrefillProgressBar.svelte   | 22 ++++++++++++++++++++--
 dashboard/src/lib/stores/app.svelte.ts             |  3 +++
 2 files changed, 23 insertions(+), 2 deletions(-)

diff --git a/dashboard/src/lib/components/PrefillProgressBar.svelte b/dashboard/src/lib/components/PrefillProgressBar.svelte
index ea08d11d..c07be12c 100644
--- a/dashboard/src/lib/components/PrefillProgressBar.svelte
+++ b/dashboard/src/lib/components/PrefillProgressBar.svelte
@@ -14,6 +14,21 @@
       : 0,
   );
 
+  const etaText = $derived.by(() => {
+    if (progress.processed <= 0 || progress.total <= 0) return null;
+    const elapsedMs = performance.now() - progress.startedAt;
+    if (elapsedMs < 200) return null; // need a minimum sample window
+    const tokensPerMs = progress.processed / elapsedMs;
+    const remainingTokens = progress.total - progress.processed;
+    const remainingMs = remainingTokens / tokensPerMs;
+    const remainingSec = Math.ceil(remainingMs / 1000);
+    if (remainingSec <= 0) return null;
+    if (remainingSec < 60) return `~${remainingSec}s remaining`;
+    const mins = Math.floor(remainingSec / 60);
+    const secs = remainingSec % 60;
+    return `~${mins}m ${secs}s remaining`;
+  });
+
   function formatTokenCount(count: number | undefined): string {
     if (count == null) return "0";
     if (count >= 1000) {
@@ -40,8 +55,11 @@
       style="width: {percentage}%"
     ></div>
   </div>
-  <div class="text-right text-xs text-exo-light-gray/70 mt-0.5 font-mono">
-    {percentage}%
+  <div
+    class="flex items-center justify-between text-xs text-exo-light-gray/70 mt-0.5 font-mono"
+  >
+    <span>{etaText ?? ""}</span>
+    <span>{percentage}%</span>
   </div>
 </div>
 
diff --git a/dashboard/src/lib/stores/app.svelte.ts b/dashboard/src/lib/stores/app.svelte.ts
index 03379cee..c10c8e0a 100644
--- a/dashboard/src/lib/stores/app.svelte.ts
+++ b/dashboard/src/lib/stores/app.svelte.ts
@@ -281,6 +281,8 @@ export interface TokenData {
 export interface PrefillProgress {
   processed: number;
   total: number;
+  /** Timestamp (performance.now()) when prefill started. */
+  startedAt: number;
 }
 
 export interface Message {
@@ -2464,6 +2466,7 @@ class AppStore {
             this.prefillProgress = {
               processed: inner.processed_tokens,
               total: inner.total_tokens,
+              startedAt: this.prefillProgress?.startedAt ?? performance.now(),
             };
           },
         },

← addf73a1 Add support for Ollama API (#1560)  ·  back to Exo  ·  fix: enable psutil fallback for memory monitoring when macmo e32b649d →