feat(worker): terminate pruned worker prompts with a hidden no-op sink (#99)
Worker prompts drop everything downstream of DistributedCollector / UltimateSDUpscaleDistributed, so the master re-added a PreviewImage (or PreviewAudio) to give ComfyUI an output node. That preview wrote every result frame as a PNG into the worker's temp directory: for a 362-frame 1344x768 video, ~2 GB per run (RAM when temp is on tmpfs) plus CPU time spent PNG-encoding frames nobody looks at. - Add DistributedWorkerSink: an output node that discards its input. It is is_dev_only, so it stays out of node search and menus. - The master probes each worker with GET /object_info/DistributedWorkerSink while preparing its prompt and uses the sink when it is registered. Workers on older versions (or a failed probe) keep the PreviewImage / PreviewAudio terminator, so mixed versions keep working. Verified with a real master plus one updated and one old worker: the updated worker ran EmptyImage -> Collector -> Sink and left its temp directory empty; the old worker still received PreviewImage. Claude-Session: https://claude.ai/code/session_0166t8T3CVaxZkJbP2RtSpo6 Co-authored-by: ymiyad <cab-13-might@icloud.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
ymiyad
Claude Opus 5.5
parent
9345a8e71b
commit
40678a51c4
@@ -143,6 +143,30 @@ async def fetch_worker_path_separator(worker, trace_execution_id=None):
|
||||
return None
|
||||
|
||||
|
||||
async def fetch_worker_has_node(worker, node_class, trace_execution_id=None):
|
||||
"""Best-effort check that a worker has ``node_class`` registered.
|
||||
|
||||
Uses ComfyUI's ``GET /object_info/{node_class}``, which returns ``{}`` for
|
||||
unknown classes. Any failure counts as "not available" so callers fall
|
||||
back to nodes every ComfyUI install has.
|
||||
"""
|
||||
url = build_worker_url(worker, f"/object_info/{node_class}")
|
||||
session = await get_client_session()
|
||||
try:
|
||||
async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
|
||||
if resp.status != 200:
|
||||
return False
|
||||
payload = await resp.json()
|
||||
return isinstance(payload, dict) and node_class in payload
|
||||
except Exception as exc:
|
||||
message = f"Failed to probe node '{node_class}' on worker {worker.get('id')}: {exc}"
|
||||
if trace_execution_id:
|
||||
trace_debug(trace_execution_id, message)
|
||||
else:
|
||||
debug_log(f"[Distributed] {message}")
|
||||
return False
|
||||
|
||||
|
||||
async def _upload_media_to_worker(worker, filename, file_bytes, file_hash, mime_type, trace_execution_id=None):
|
||||
"""Upload one media file to worker iff missing or hash-mismatched."""
|
||||
session = await get_client_session()
|
||||
|
||||
@@ -328,8 +328,19 @@ def _find_delegate_master_retained_upstream_nodes(prompt_obj, start_ids):
|
||||
return connected
|
||||
|
||||
|
||||
def prune_prompt_for_worker(prompt_obj):
|
||||
"""Prune worker prompt to distributed nodes and their upstream dependencies."""
|
||||
WORKER_SINK_CLASS = "DistributedWorkerSink"
|
||||
|
||||
|
||||
def prune_prompt_for_worker(prompt_obj, use_worker_sink=False):
|
||||
"""Prune worker prompt to distributed nodes and their upstream dependencies.
|
||||
|
||||
Removing downstream nodes can leave the worker prompt without an output
|
||||
node. One is re-added after each such distributed node: the hidden
|
||||
``DistributedWorkerSink`` when ``use_worker_sink`` is set (the worker must
|
||||
have it registered), otherwise PreviewImage/PreviewAudio for workers
|
||||
running older versions. The preview nodes write every frame to the
|
||||
worker's temp directory, which the sink avoids.
|
||||
"""
|
||||
collector_ids = find_nodes_by_class(prompt_obj, "DistributedCollector")
|
||||
upscale_ids = find_nodes_by_class(prompt_obj, "UltimateSDUpscaleDistributed")
|
||||
distributed_ids = collector_ids + upscale_ids
|
||||
@@ -364,7 +375,13 @@ def prune_prompt_for_worker(prompt_obj):
|
||||
and len(inputs["audio"]) == 2
|
||||
)
|
||||
|
||||
if image_connected:
|
||||
if use_worker_sink and (image_connected or audio_connected):
|
||||
pruned_prompt[next_id()] = {
|
||||
"inputs": {"value": [dist_id, 0 if image_connected else 1]},
|
||||
"class_type": WORKER_SINK_CLASS,
|
||||
"_meta": {"title": "Distributed Worker Sink (auto-added)"},
|
||||
}
|
||||
elif image_connected:
|
||||
preview_id = next_id()
|
||||
pruned_prompt[preview_id] = {
|
||||
"inputs": {"images": [dist_id, 0]},
|
||||
|
||||
Reference in New Issue
Block a user