Skip to Content
Ⅱ · 执行引擎06 · 动态拓扑调度

06 · 动态拓扑调度

普通 DAG 可以一次拓扑排序后逐个执行。ComfyUI 不行:某节点 stage 之后可能发现还需要一个懒输入,也可能返回运行时子图,或挂起一个异步任务。调度器必须允许选中、退回、增加依赖、再选中。

两层调度,不要混淆

PromptQueue 决定“哪个 Prompt 先跑” 进程级、heap priority ExecutionList 决定“当前 Prompt 哪个节点跑” 请求级、动态拓扑

本章只讲第二层。

PromptExecutor 的外循环

execution.pyL730-844一次 Prompt 的执行生命周期。

简化后:

with torch.inference_mode(): dynamic_prompt = DynamicPrompt(prompt) prepare_caches(dynamic_prompt) execution_list = ExecutionList(dynamic_prompt, output_cache) for output in good_outputs: execution_list.add_node(output) while not execution_list.is_empty(): node = await execution_list.stage_node_execution() result = await execute(node) if result is PENDING: execution_list.unstage_node_execution() elif result is SUCCESS: execution_list.complete_node_execution() else: break

这里最重要的不是 while,而是三态返回:SUCCESS / FAILURE / PENDING。PENDING 代表“没有失败,但目前还不能把节点从图中删除”。

TopologicalSort 保存什么

概念上有:

pendingNodes 仍需完成的节点 blockCount 每个节点还有多少强上游未完成 blocking 一个上游完成后会释放哪些下游 externalBlocks 尚未结束的外部异步阻塞数 unblockedEvent asyncio 等待唤醒

当加入一个输出节点,调度器递归添加它的强依赖。缓存命中的上游可直接提供结果,不必成为 pending 计算。

comfy_execution/graph.pyL106-191动态可修改的拓扑基础结构。

stage / unstage / complete

stage

stage_node_execution() 找到 blockCount == 0 的 ready nodes,从中选一个,写入 staged_node_id。同一时刻最多一个节点处于 staged。

unstage

如果执行函数返回 PENDING,清除 staged 标记,但不从 pending 图删除节点。执行器已在此前添加新的依赖或 external block,下一次循环会先处理它们。

complete

成功时 pop_node() 释放下游 block,清理为该消费者维护的 execution-local cache link,然后清空 staged。

这个小状态机是动态能力的支点。若 stage 后只允许成功或失败,懒求值和 expand 都必须被塞进节点内部,运行时就失去可见性。

ready nodes 不代表随便挑

任何 ready node 都满足依赖正确性,ux_friendly_pick_node() 再做体验启发式:

  1. ready 中有输出节点,优先它;
  2. ready 中有 async 节点,优先尽早启动;
  3. 某节点直接阻塞输出节点,优先它;
  4. 某节点距离输出两跳,优先它;
  5. 否则取列表第一个。
comfy_execution/graph.pyL248-319输出优先、异步优先与接近输出的启发式选择。

为什么输出优先不会缩短关键路径,却仍值得做

例如 VAEDecode → PreviewImage 已 ready,同时另一条远处分支也 ready。先执行 PreviewImage 让用户更早看到结果,总计算量不变,体感延迟却下降。

为什么 async 优先

异步节点往往提交远端任务或 I/O。越早启动,它越能和后续本地计算重叠。注意它不是并行执行任意 Python 节点,而是让 coroutine 进入 pending 后释放调度循环。

ExecutionList 不只存拓扑,还给每个消费者维护上游值引用:

execution_cache[to_node][from_node] = CacheEntry execution_cache_listeners[from_node] = {(to_node, socket), ...}

节点从输出 cache 被逐出时,已经建立的消费者仍可能需要它。局部引用保证本次依赖链稳定;读取时还会 write-back 到主 cache。PromptModelTracker.add 可作为 output link callback,发现链路中出现的 ModelPatcher。

comfy_execution/graph.pyL193-247消费者局部缓存、监听与 write-back。

这是缓存淘汰与正确执行之间的隔离层:全局策略可以回收,当前正在运行的边仍持有必要对象。

缓存节点如何进入调度

Prompt 开始时,执行器:

  1. 为所有原始节点计算/登记当前签名;
  2. 并发查询输出 cache;
  3. 发送 execution_cached 节点列表;
  4. 建立 ExecutionList 时把缓存可用性纳入边解析。

当某节点真正轮到 execute(),入口仍再次查询 cache。这是防守性设计:动态子图或执行期间的 cache 更新可能让状态与初始扫描不同。

一次局部重算的调度过程

正向提示词改变,负向不变:

调度器初始 pending 闭包仍从 Save 展开,但 cached nodes 可立即作为满足的边。第一个真正 ready 的可能就是 Positive encode,而非 Checkpoint。

外部阻塞

如果没有 ready node 但 externalBlocks > 0,stage_node_execution() 等待 unblockedEvent。异步任务完成后回调减少阻塞并 set event。

如果没有 ready node且没有 external block,则不是“再等等”,而是依赖环。代码溶解掉所有可消除节点,剩余的就是环中节点,并尽量把错误归因到非静态、可显示的父节点。

comfy_execution/graph.pyL229-247等待外部任务与运行时环错误。

运行时新增依赖

懒输入

节点被 stage 后,check_lazy_status() 说还需要 fallback_image:

make_input_strong_link(current_node, 'fallback_image') return PENDING unstage current_node execute fallback branch stage current_node again

expand 子图

节点返回新图,执行器添加 ephemeral nodes、把子图输出与父节点连接,记录 pending results,父节点 PENDING。子图算完后,父节点再次 stage,解析结果链接并完成。

两者都证明“拓扑不是输入时一次决定的”。

错误路径

任一节点 FAILURE 时:

  • 外循环停止,不继续下游;
  • handle_execution_error 发送结构化消息;
  • executed 与 current_outputs 让客户端知道已完成/已有缓存的范围;
  • OOM 时执行入口会卸载已加载模型并附加提示;
  • finally 仍结束 PromptModelTracker 和 cache provider 生命周期。

不会自动回滚已写文件或远端 API 副作用。节点作者应在不可逆操作前完成验证,并设计幂等键。

torch.inference_mode() 的边界

整个 Prompt 主循环位于 torch.inference_mode(),关闭 autograd 记录,符合推理场景。自定义节点若试图训练或依赖梯度,必须显式处理上下文;核心运行时不以训练工作负载为默认。

RAM pressure 在哪里插入

RAMPressure cache 模式下,每完成或挂起一个节点,外循环检查可用系统内存:

  1. 先回收不活跃 workflow 缓存;
  2. 仍不足时回收大的活跃中间结果;
  3. 再考虑释放 pinned memory;
  4. 必要时做完整 active eviction。

把检查放在节点边界,而不是任意张量操作中,既可预测又能利用刚完成节点的生命周期信息。

调度器的明确边界

  • 它不是多 GPU 任务调度器;设备选择在模型管理层。
  • 它默认一次 stage 一个节点;async 允许 I/O 重叠,不等于任意节点并行。
  • 它保证依赖顺序,不保证节点副作用可回滚。
  • 它理解缓存条目和链接,却不理解张量语义。
  • UX heuristic 影响先后,不影响依赖正确性。

可迁移的设计方法

  1. 动态 DAG 用 stage → pending/complete,不要强迫一次性拓扑序。
  2. 正确性选择与体验选择分层:先 ready set,再 heuristic。
  3. 全局可淘汰缓存之外,给执行中消费者保留局部引用。
  4. 外部 async block 用显式计数与事件,而不是 busy polling。
  5. 静态验证之后仍在运行时检查环,因为图可能增长。
Last updated on