Skip to content

Commit e19a994

Browse files
committed
fix(vllm): show the owning node idle timeout on peer model rows
Signed-off-by: nvrithik <rithikv@nvidia.com>
1 parent 2e300f2 commit e19a994

12 files changed

Lines changed: 777 additions & 10 deletions

File tree

‎desktop/docs/services-backend.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ remote control surface where supported.
155155

156156
### Engine settings
157157

158+
Engine status includes the optional `idle_timeout_s` field for engines with an
159+
idle-unload limit (0 means never unload). The engine manager's pinned `ec`
160+
status endpoint and the broker's `engine:remote-get-installed` relay preserve
161+
this owner-reported value. The bridge uses it for remote model expiry labels,
162+
scoped to the owning node and engine, and updates them on the existing remote
163+
status refresh. No idle-timeout field is added to discovery records.
164+
158165
Ports and the engine arguments are one authoritative, revisioned record owned by
159166
the node running the engine. The broker exposes `engine:{get,preview,apply}-settings`
160167
and relays a request naming another node to that peer's engine manager, so a

‎desktop/docs/services-parity.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,11 @@ first request or a Load loads the model, it unloads after an idle limit (five
196196
minutes by default, `get_idle_timeout` and `set_idle_timeout`, shown as the
197197
model row's expiry control), and Eject (`unload_model`, a `stop_engine` action)
198198
unloads it on demand while the engine stays on; the card shows an idle engine
199-
as "Idle · loads on next request" (`idle` on the engine status). Its model list
199+
as "Idle · loads on next request" (`idle` on the engine status). Model rows use
200+
the owning node's `idle_timeout_s` from engine status for the expiry labels,
201+
including when viewed on a peer. Remote labels update on the existing engine
202+
status refresh (every 20 seconds); until a value is reported, vLLM uses its
203+
five-minute default. Its model list
200204
(`list_models` on disk) is kept while stopped. It downloads and deletes
201205
through the shared Hugging Face cache (`pull_model` is an `hf_pull` action,
202206
`delete_model` an `hf_cache` delete that refuses the model an engine process

‎desktop/src/electron/service-bridge/modular-state.ts‎

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ interface RemoteEngineFacts {
8484
running: boolean
8585
healthy: boolean
8686
port: number
87+
idleTimeoutSeconds?: number
88+
}
89+
90+
function readIdleTimeoutSeconds(value: JsonValue | undefined): number | undefined {
91+
return typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 86400
92+
? value
93+
: undefined
8794
}
8895

8996
interface ModularNode {
@@ -1402,6 +1409,10 @@ class ModularBridgeState {
14021409
port: numberValue(obj.port),
14031410
idle: booleanValue(obj.idle)
14041411
})
1412+
const seconds = readIdleTimeoutSeconds(obj.idle_timeout_s)
1413+
if (EnginesWithIdleTimeout.includes(engineType) && seconds !== undefined) {
1414+
this.setEngineIdleSeconds(engineType, seconds)
1415+
}
14051416
// A fresh authoritative state is the resolution of whatever op was in
14061417
// flight (start/stop done, install `done`+installed, uninstall removed).
14071418
// Drop the optimistic status without re-emitting here — the emit below
@@ -1411,10 +1422,15 @@ class ModularBridgeState {
14111422
}
14121423

14131424
/** The expiry the models of an engine show: its idle limit, or the default. */
1414-
private expiryFor(engineType: EngineType): ModelExpiry {
1415-
const seconds = this.idleSeconds.get(engineType)
1425+
private expiryFor(engineType: EngineType, nodeId = this.selfId): ModelExpiry {
1426+
if (!EnginesWithIdleTimeout.includes(engineType)) return '10m'
1427+
const seconds =
1428+
nodeId && nodeId !== this.selfId
1429+
? this.remoteEngineFacts.get(this.remoteOpKey(nodeId, engineType))
1430+
?.idleTimeoutSeconds
1431+
: this.idleSeconds.get(engineType)
14161432
if (seconds !== undefined) return idleSecondsToExpiry(seconds)
1417-
return EnginesWithIdleTimeout.includes(engineType) ? DefaultIdleExpiry : '10m'
1433+
return DefaultIdleExpiry
14181434
}
14191435

14201436
/**
@@ -1877,7 +1893,8 @@ class ModularBridgeState {
18771893
installed: booleanValue(engineObj.installed),
18781894
running: booleanValue(engineObj.running),
18791895
healthy: booleanValue(engineObj.healthy),
1880-
port: numberValue(engineObj.port)
1896+
port: numberValue(engineObj.port),
1897+
idleTimeoutSeconds: readIdleTimeoutSeconds(engineObj.idle_timeout_s)
18811898
})
18821899
seen.add(engineType)
18831900
}
@@ -1908,7 +1925,8 @@ class ModularBridgeState {
19081925
installed: booleanValue(obj.installed),
19091926
running: booleanValue(obj.running),
19101927
healthy: booleanValue(obj.healthy),
1911-
port: numberValue(obj.port)
1928+
port: numberValue(obj.port),
1929+
idleTimeoutSeconds: readIdleTimeoutSeconds(obj.idle_timeout_s)
19121930
})
19131931
this.emitRemoteEngineStatus(nodeId, engineType)
19141932
}
@@ -2088,11 +2106,12 @@ class ModularBridgeState {
20882106

20892107
private toEngineModels(node: ModularNode, engine: ProxyEngine): EngineModels {
20902108
const loaded = loadedNamesForEngine(node, engine)
2109+
const expiry = this.expiryFor(engine, node.id)
20912110
return {
20922111
engineType: engine,
20932112
nodeId: node.id,
20942113
models: this.modelsForEngine(node, engine).map(name =>
2095-
modelItem(name, loaded.has(name))
2114+
modelItem(name, loaded.has(name), expiry)
20962115
)
20972116
}
20982117
}

0 commit comments

Comments
 (0)