ComfyUI-MuteBypassSwitch
Five switch nodes that keep the graph running when an upstream node is muted (Ctrl+M): mute-bypass, A/B, multi-any merge, multi-channel merge and output fanout. Pure reference hand-off - no copying, conversion or device moves.
English | 中文
静音旁路 / 多路汇聚开关 (Mute Bypass Switch)
给 ComfyUI 的一套小开关:上游节点被静音/禁用(Mute,Ctrl+M)之后,数据照样能往下走, 而且交出去的就是原来那个对象 —— 不复制、不转换、不 detach、不搬 CPU,一个字节都不改。
2.0.0 —— UI 默认英文 + 可选翻译。 节点名、tooltip、右键菜单和控制台提示都改成英文了;
同时附带 locales/en + locales/zh,ComfyUI 语言设成中文时界面仍然是中文。
输出分发开关的控件键从中文改成了 outputs_per_input / output_<n>,选项值改成 2/3/4
和 on/off;1.x 存下来的工作流需要把这两个控件重新选一次。
一共五个节点:
| 节点 | 干什么 | |---|---| | 多通道汇聚开关(推荐:一条支路有多个产出时用它) | 每个通道各自挑"活着的哪一路",通道数量自动加(+2 输入 / +1 输出),最多 8 个通道 | | 多路汇聚开关(推荐:只要一路数据时用它) | 想接几路接几路(输入口会自动长出来),输出有值的那一路 | | 输出分发开关(多通道的反向:一对多) | 一个输入配几个输出(2/3/4 可选),每个输出可单独开关,输出口自动增减 | | 静音旁路开关 | 主线 / 旁路两路,主线被静音时自动走旁路 | | 任意 A/B 开关 | 任意类型二选一 |
只想装一个、只想记一个: 一条支路只产出一个东西 → 用「多路汇聚开关」;产出好几个东西(图 + 蒙版 + 条件…)→ 用「多通道汇聚开关」。
多通道汇聚开关的实际样子(通道 1、2 都接满,自动长出通道 3):

输出分发开关的实际样子(A/B 两路输入 → 4 个输出,out_1、out_3 各自接了一个预览):

多路汇聚开关的实际样子(4 路接满、自动多出第 5 个空口):

安装:把
ComfyUI-MuteBypassSwitch整个文件夹放进ComfyUI\custom_nodes\(在custom_nodes里git clone https://github.com/Davonez/ComfyUI-MuteBypassSwitch.git也行), 重启 ComfyUI,然后浏览器按F5刷新一次,节点就出现在菜单里 (分类Switch,搜「汇聚 / Mute / Multi」都能找到)。更细的步骤见 INSTALL.txt。
1. 为什么原方案会被卡死(问题根因)
ComfyUI 的「静音」不是把节点跳过就算完,而是让这个节点整个不进入执行图。 它输出端那根线,在提交任务时也跟着一起消失了。于是下游节点的输入就没人喂了:
-
如果下游这个输入是必填的 → 引擎直接判定整个任务非法,报错,一个节点都不跑:
Required input is missing -
如果下游这个输入是可选的 → 引擎会安静地跳过它,节点照跑(但少了数据,结果可能不对)。
这两条规则就写在引擎源码里(本机版本:ComfyUI/execution.py 的 validate_inputs):
if x not in inputs:
if input_category == "required":
...
"message": "Required input is missing", # 必填缺失 -> 整体失败
continue # 可选缺失 -> 静默跳过
所以「静音一个中间节点 → 下游报错卡住」是引擎的既定行为,不是哪个插件的 bug。 能救的办法只有一条:在下游那个必填输入前面,插一个"永远有值交得出来"的节点。
本插件就是这个节点。它的两个输入都声明成 可选 + 任意类型 *,所以:
- 上游被静音 → 本节点不会因为缺输入而报错(可选输入,引擎直接放过);
- 主线没值了 → 本节点把旁路那一路上来的数据原样转交给下游。
* 通配符是引擎官方支持的 Any 类型(comfy_execution/validation.py 里
IO.AnyType.io_type == "*" 直接放行),所以 IMAGE / LATENT / CONDITIONING / MODEL / CLIP /
VAE / MASK / 字符串 / 数字…… 都能连,不用为每种类型单独装一个开关。
2. 怎么接线(照着你画的那张图)
┌────────────────────────────────┐
上游 ─────►│ 某个节点(平时会被你静音的) │──────┐ main
│ └────────────────────────────────┘ │
│ ▼
│ ┌─────────────────────┐
└───────────────────────────────────►│ 静音旁路开关 │──────► 下游节点
bypass │ switch = 旁路 开 │ (必填输入接这里)
└─────────────────────┘
三步:
- 上游(比如 Load Image、你的 LoRA 节点、某个后处理节点)的输出,拉两根线:
一根接「被静音节点」的输入,一根接开关的
bypass; - 被静音节点的输出 → 接开关的
main; - 开关的输出
data→ 接下游节点原来那个接口。
平时:main 有值,开关原样转交 main,等于线直接连着,行为完全不变。
被你静音时:main 那根线没了,开关自动改走 bypass,把上游的原始数据交给下游,工作流继续跑。
3. 节点参数
静音旁路开关 (Mute Bypass Switch)
| 参数 | 类型 | 说明 |
|---|---|---|
| main | 输入,可选,任意类型 | 主线:来自可能被你静音的那个节点 |
| bypass | 输入,可选,任意类型 | 旁路:主线失效时接管的那一路(一般直接接上游源头) |
| switch | 开关,默认 旁路 开 | 开:主线缺失时允许走旁路;关:只走主线(等于把旁路线剪断) |
| route | 下拉,默认 auto | auto 主线优先、缺了走旁路 / main 只走主线 / bypass 只走旁路(调试用) |
| data | 输出,任意类型 | 原样透传进来的那个对象(同一个引用) |
route 的完整行为表:
| 主线 | 旁路 | switch | route | 输出 |
|---|---|---|---|---|
| 有值 | 有/无 | 任意 | auto | 主线(原对象) |
| 空 | 有值 | 开 | auto | 旁路(原对象)← 这就是"被静音也能过" |
| 空 | 有值 | 关 | auto | 空(恢复原样,下游还是拿不到) |
| 空 | 空 | 任意 | auto | 空 + 控制台警告 |
| 有值 | 有值 | 任意 | main | 主线 |
| 空 | 有值 | 任意 | main | 空(尊重"只走主线") |
| 有值 | 有值 | 任意 | bypass | 旁路 |
| 有值 | 空 | 任意 | bypass | 退回主线(绝不阻断) |
注意
0/""/False这种"假值"仍然算有值,不会被误判成缺失。
任意 A/B 开关 (Any A/B Switch)
顺手给的一个通用二选一:a / b 两路任意类型输入,which 选 A 或 B,
选中的那路原样输出;如果选中的那路没接,自动退回另一路。适合做 A/B 对比、临时旁路。
多路汇聚开关 (Multi Any Switch)
想接几路接几路 —— 多个任意类型的输入收成一路,输出其中有值的那一路。
第 1 路 ──┐
第 2 路 ──┤
第 3 路 ──┼──►[ 多路汇聚开关 ]──► data ──► 下游
第 4 路 ──┤ (自动挑有值的)
第 N 路 ──┘
| 参数 | 类型 | 说明 |
|---|---|---|
| input_1 / input_2 … | 输入,可选,任意类型 | 第几路就接第几个口。最后一路接上线后会自动多出一个空口,可以一直加(上限 32 路) |
| switch | 开关,默认 汇聚 开 | 开:多路里挑有值的那一路;关:只认第 1 路(等于没这个节点) |
| pick | 下拉,默认 first | 多路都有值时用哪一路:first 最上面那路 / last 最下面那路 |
| force | 数字,默认 0 | 自己指定走哪一路:0 = 自动挑;1~16 = 强制第 N 路(那一路没值就交空,不再自动换路) |
| data | 输出,任意类型 | 被选中的那一路,原样透传(同一个对象) |
行为:按插槽顺序,第一个「有值」的胜出。被静音的上游不会报错,直接跳过它往下选。
不想自动挑:把 force 填成 2,就永远走第 2 路(哪怕第 1 路有值也照走第 2 路)。
右键节点有三个手动项(不用先接线也能用):
| 菜单项 | 作用 |
|---|---|
| + 增加一路输入 | 立刻多一个空口 |
| - 删掉最后一路空口 | 只删动态加的、且没连线的口(input_1/input_2 不动) |
| 整理空输入口 | 一次把多余空口收掉,只留「已连线的 + 1 个空位」 |
典型接法(用一路兜底):把「被静音的那条支路」接 input_1,
「上游源头 / 便宜替身」接 input_2;平时走 input_1,一被静音就自动落到 input_2。
几个实现上的关键点(也是为什么它敢这么长口):
- 后端只在
INPUT_TYPES里声明了input_1、input_2,第 3 路以后是前端按需加的插槽; 引擎get_input_data()对「没声明但被连线连上的输入」照样解析成 kwargs 传进来, 所以开多少路都不会报「未知输入」。 - 前端扩展只追加插槽、绝不重排。因为工作流存盘时
graph.links里的target_slot是插槽数组下标,而重新加载时前端是按「先建声明的口、再建控件行、最后追加多余的口」 来重建的;一旦重排,下标就和存档对不上,下次打开会接错口。 - 多余的口在画布上会自动排在
switch/pick这两行上面,不影响观感。
多通道汇聚开关 (Multi Channel Switch)
一条支路往往不只产出一个东西(图 + 蒙版 + 条件…)。这个节点就是每个通道各自汇聚: 某条支路被静音,只有它负责的那个通道自动换到下一路,其它通道完全不受影响。
通道1: a ─┐ ┌─► ch1 (图)
b ─┴─►[ 通道1 挑有值的 ]──────────┘
通道2: a ─┐ ┌─► ch2 (蒙版)
b ─┴─►[ 通道2 挑有值的 ]──────────┘
通道3: a …(接满通道2 就自动长出来)
| 参数 | 说明 |
|---|---|
| ch{k}_a / ch{k}_b | 第 k 个通道的主用口 / 兜底口,任意类型、可选 |
| switch | 总开关:关掉就只认每通道的 a 路 |
| pick | 同一通道里多路都有值时用哪一路:first / last |
| force | 总强制:auto = 不强制(默认)/a、b、c = 所有通道都只看这一路 |
| ch{k}_force | 每个通道各自的强制:auto = 跟随总 force/自动挑;a、b、c = 这个通道只看这一路 |
| ch1 … ch8 | 每个通道一个输出口,各自独立 |
强制选择就是"我偏要走这条支路",指定的那一路是空的就交空、绝不偷偷替你去挑。
优先级:通道自己的 ch{k}_force > 总 force > 自动挑(switch/pick)
| 场景 | ch1_force=auto、force=auto | ch1_force=b | force=b(通道仍是 auto) |
|---|---|---|---|
| 只有 a 有值 | ch1 = a | ch1 = 空(不回落) | ch1 = 空(不回落) |
| a、b 都有值 | ch1 = a | ch1 = b | ch1 = b |
| 只有 b 有值 | ch1 = b | ch1 = b | ch1 = b |
通道的
ch{k}_force下拉只显示已经长出来的通道:1 个通道就 1 行,长到 3 个通道就有 3 行。 后端虽然静态声明了 8 个,但前端会把多余的从node.widgets里摘掉/按需加回来 (实测:运行时addWidget出来的控件,值照样进 prompt、也照样存盘)。
输入侧和输出侧都会自动加路:一个通道的 a、b 都接上了 → 自动多出下一个通道的 2 个输入口 和 1 个输出口(最多 8 个通道)。画布上默认只显示 1 个通道,不占地方。
右键菜单还能手动「+ 加一个通道(+2 输入 / +1 输出)」「- 删掉最后一个空通道」。
输出分发开关 (Output Fanout Switch)
多通道汇聚的反向。结构是「一个输入配几个输出」,节点上只有两个控件:

A 节点 ──► in_1 ──┐ ┌─► out_1 ┐
├─[ 输出分发开关 ]───┼─► out_2 ┘ 这两个跟 A
B 节点 ──► in_2 ──┘ ├─► out_3 ┐
└─► out_4 ┘ 这两个跟 B
| 控件 | 说明 |
|---|---|
| 每个输入配几个输出 | 2 个 / 3 个 / 4 个。选 2:A 出 out_1/out_2、B 出 out_3/out_4;选 3:A 出 out_1~out_3、B 出 out_4~out_6 |
| 输出1 输出2 … | 每个输出一行,只有 开 / 关。不想要哪个输出就选「关」 |
就这两样,不用再选「跟哪一路」——配对是自动的:
第 k 个输出跟第 向上取整(k ÷ 每路个数) 路输入。
- 接上第 1 路输入 → 自动出
每路个数个输出 - 再接第 2 路 → 再多出
每路个数个(跟第 2 路) - 最多 8 路输入;输出口会跟着输入口自己长出来
为什么输出要一次声明 32 个:ComfyUI 的
RETURN_TYPES是静态的,输出口数量不能像输入那样 动态长。所以后端一次声明够用的数量,前端只把用到的露出来(其余removeOutput收起来), 生长时再放出来 —— 因为始终只动数组末尾,graph.links里的下标不会错位,存盘安全。
注意:把某个输出设成「关」以后,接在它下游的节点会拿到空、报错(一个输出口总得有个值)。 要"这次不走这条分支",把那几个下游节点静音就行。
4. 为什么敢说"不改原数据"
FUNCTION 里只有一行 return (value,) —— 把传进来的对象引用直接塞回输出,
中间没有 copy()、没有 torch 运算、没有 list() 打包、没有 .to()、没有 detach()。
离线单测里用"同一个对象 is 判断 + 执行前后字节比对"验过(见 tests/test_offline.py)。
ComfyUI 那边拿到的就是上游节点原来产出的那个 tensor / 对象,颜色、精度、元数据全都不变。
5. 装 / 卸
- 安装:把整个
ComfyUI-MuteBypassSwitch文件夹放进ComfyUI\custom_nodes\,重启 ComfyUI。 - 卸载:删掉这个文件夹,重启。不会留下任何配置、不会改你的工作流。
- 不依赖任何第三方包(只用标准库),不需要
pip install。
节点在菜单里的位置:右键 → Add Node → Switch,或者双击画布搜
静音 / mute / switch。
6. 常见问题
Q: 装完菜单里找不到节点?
先确认控制台没有 IMPORT FAILED 报错,再 F5 刷新浏览器(节点列表是页面加载时拉的)。
中文搜「静音」、英文搜 MuteBypassSwitch 都能出来。
Q: 插上开关了,怎么还是报 Required input is missing?
这个报错说的是哪个输入缺,看 details 里的字段名。两种常见情况:
- 下游的输入还直接连在"被静音节点"上,没改接到开关的
data输出; - 缺的是另一个接缝(另一条线也被静音了)。一个开关只能救它自己那一个输入, 一条线一个开关;两个接缝就放两个,或者直接用「多路汇聚开关」把几条分支汇成一路。
Q: 为什么关了开关还是会报错? 关掉开关 = 恢复成"没有这个节点时"的行为:主线没值,交不出东西,下游当然还是拿不到。 这个开关的意义就是默认打开,需要对照原始故障时再关。
Q: 会不会变慢 / 影响出图质量? 不会。它只是一次引用转交,没有张量计算、没有显存拷贝,也不改 workflow 的其余部分。
Q: 被静音的是 Load Image 这种"源头",我旁路接谁?
源头没有上游可接,就接一个便宜的替身节点(比如 EmptyImage 给 IMAGE、
CLIPTextEncode 给 CONDITIONING、Empty Latent Image 给 LATENT),
把它接到 bypass。这样"没图"的时候也能跑通,不会卡死。
Q: 能不能用它做真正的"断路"(让下游彻底不跑)? 不能。ComfyUI 的输出必须有值,"断路"是整组节点一起静音的事(那种场景用 rgthree 的 Fast Groups Bypasser 或直接 Ctrl+M 整组更合适)。本节点只负责"接缝不断"。
7. 验证过什么(不是"应该能用",是真跑过)
在本机这套环境里跑完了三层验证,全部通过:
① 离线单测 139 项(不需要启动 ComfyUI,python tests\test_offline.py)
main/bypass确实在optional里,类型是*,没有任何必填的链路输入;- 透传的是同一个对象(
out is in为真),执行前后字节/属性都没变; 0/""/False/[]这类假值不会被误判成"缺失";- 12 种开关×路由×输入缺失组合的输出都符合上面那张行为表;
- 多路汇聚:kwargs 顺序打乱也按插槽号排、
input_10排在input_2后面(数字比较)、pick=last、只有一路有值、全空、关汇聚、无关 kwargs 全部覆盖; - 多通道汇聚:通道之间互不影响、
ch1_b兜底、第 3 路候选c、第 8 通道可用、 第 9 通道越界被忽略、输出恒为 8 个值; - 强制选择(
force):多路填 2 就无视第 1 路、第 2 路没接就交空不回退、优先于pick/switch; 多通道force=b让所有通道都走 b、某通道 b 没接就只那个通道交空、切回auto恢复自动; 分发节点:1 路输入 -> 前 2 个输出有值、2 路输入 -> A A B B、配 3 个 -> A A A B B B、关掉某个输出 -> 它交空、选项写坏也能按 2 个兜住。
② 后端真机 API 26 项(python tests\test_live_api.py,往 8188 真提交任务)
- A. 不给
main(= 上游被静音)+bypass=红 → 任务成功,产物 64x64 纯红; - B.
main=蓝 +bypass=红 → 产物是蓝(主线优先,平时行为不变); - C.
switch=关+ 无main→ 任务失败(说明开关真的能控制旁路); - D.
route=bypass+ 两路都有 → 产物是红(旁路优先); - E. 两路都缺 → 任务失败,但不是
Required input is missing; - F. 多路只接第 3、4 路(1、2 路等于被静音)→ 用第 3 路 64x64 蓝;
- G. 多路 1~4 全接 → 用第 1 路 32x32 红;
- H. 多路
pick=last+ 全接 → 用第 4 路 80x80 白; - I. 多路只接第 2 路 → 用第 2 路 48x48 绿;
- J. 多通道:通道1 的 a 缺失 → 通道1 用 b(48x48 绿),通道2 用 a(64x64 蓝);
- K. 多通道:通道1 a/b 都有 → 用 a(32x32 红),通道2 只有 b → 用 b(80x80 白);
- L. 多通道开到第 3 个通道,三路各自取 a(32红 / 64蓝 / 96灰);
- M. 多通道
force=b:a 有值也强制走 b(48绿 / 80白); - N. 多通道
force=a:两块都走 a(32红 / 64蓝); - O. 多通道
force=b但通道1 的 b 没接 → 交空、下游失败(不自动回退到 a); - P. 多路
force=2:无视第 1 路,强制用第 2 路(48绿); - Q. 多路
force=2但只接了第 1 路 → 交空、下游失败(不自动回退); - U. 分发:1 路输入 + 配 2 个 → out_1/out_2 都拿到它;
- V. 分发:配 3 个 → out_1..out_3 都是它;
- W. 分发:2 路输入 + 配 2 个 → 红、红、绿、绿;
- X. 分发:把「输出2」关掉 → 接在 out_2 上的下游拿不到数据,如预期失败;
- Y. 分发:配 3 个 + 2 路输入 → 红红红、绿绿绿;
- Z. 分发:关掉输出2~8,out_1 仍照常输出(32红)。
(F/H 的
input_3、input_4和 J/K/L 的ch2_*、ch3_*在INPUT_TYPES里 根本没声明,能跑通就证明动态输入成立。)
③ 前端真机 81 项(无头 Edge + CDP,加载真实 8188 页面)
- 静音旁路开关 11 项:
*通配插槽双向都能连;把主线上游设成静音(mode=2,等价 Ctrl+M)后, 前端graphToPrompt()出来的 prompt 里main这根线真的消失了而bypass还在; 这种状态下真的点排队:任务success,产物 64x64(旁路那路),而不是主线的 128x128; 取消静音后main又回到 prompt 里。 - 多路汇聚开关 21 项:初始 2 个口;每接一路自动多一个空口(1→2→3→4→5);
prompt 里确实带上没声明的
input_3/input_4;4 路全接时产物 32x32, 静音第 1 路后变 48x48,再静音第 2 路后变 64x64;右键加/删一路; 存盘→加载→再存盘,每根线仍然指向input_1..input_4(不会被重排弄错)。 - 多通道汇聚开关 29 项:刚拖进来只露 1 个通道(2 输入 / 1 输出,后端声明的 8 个输出被收起);
通道接满自动 +2 输入 +1 输出(长到 3 个通道);不静音时 ch1=32x32 / ch2=64x64,
静音通道1的 a 后 ch1 变 48x48 而 ch2 不受影响仍是 64x64;
存盘→加载后 3 个通道、6 个输入口、3 个输出口和连线全在;右键加/删通道正常;
画布上把总
force切到b→ 产物变 48/80(无视 a 有值),切回auto→ 恢复自动挑; 每通道下拉:ch1_force=b时只有通道1 变 48,通道2 仍 64;两个各自设 a/b 也互不干扰;ch{k}_force只显示已长出来的通道(1 通道 1 行、3 通道 3 行)。 - 输出分发开关 20 项:刚拖进来只有「每个输入配几个输出」+ 两行「开/关」,1 输入 2 输出; 接上 A 后输入侧多一个空位;两个输出各接 SaveImage → 都是 A;再接上 B → 输出自动变 4 个 并多出「输出3」「输出4」;把「配几个」改成 3 个 → 输出变 6 个、out_1..3 跟 A、out_4 跟 B; 存盘→加载后口数、下拉、连线都在,再排队仍正确。
环境:ComfyUI-aki-v3(前端 LiteGraph + comfyAPI 新架构,节点总数 3763)、
Python 3.13、Edge 无头 + CDP。
8. 文件结构
ComfyUI-MuteBypassSwitch/
├─ __init__.py # 节点注册 + WEB_DIRECTORY
├─ nodes.py # 五个节点的全部逻辑,仅用标准库
├─ pyproject.toml # 包信息(ComfyUI Registry 用)
├─ LICENSE # MIT
├─ INSTALL.txt # 给不看 README 的人看的 30 秒安装说明
├─ web/
│ ├─ multi_any_switch.js # 多路汇聚:自动加输入口 / 右键加减
│ ├─ multi_channel_switch.js # 多通道汇聚:自动加通道(输入+输出一起长)
│ └─ output_fanout_switch.js # 输出分发:一对多,每个输出自己选源
├─ preview_multi.png # 多路汇聚开关的截图
├─ preview_channel.png # 多通道汇聚开关的截图
├─ preview_fanout.png # 输出分发开关的截图
├─ README.md # 本文件
├─ README.en.md # English version
└─ tests/
├─ test_offline.py # 离线单测 139 项,不用启动 ComfyUI
├─ test_live_api.py # 后端真机 26 项,需要 ComfyUI 在 8188 运行
├─ test_frontend_cdp.js # 静音旁路开关 前端真机 11 项
├─ test_multi_frontend_cdp.js # 多路汇聚开关 前端真机 21 项
├─ test_channel_frontend_cdp.js# 多通道汇聚开关 前端真机 29 项
└─ test_fanout_frontend_cdp.js # 输出分发开关 前端真机 20 项
跑测试:
# 离线(任意 3.10+ 都行)
python tests\test_offline.py
# 后端(用整合包自带 python,ComfyUI 要保持运行)
<ComfyUI 整合包目录>\python\python.exe tests\test_live_api.py
# 前端(会自动开一个无头 Edge,用完自己关)
node tests\test_frontend_cdp.js
node tests\test_multi_frontend_cdp.js
node tests\test_channel_frontend_cdp.js
node tests\test_fanout_frontend_cdp.js
License: MIT | 见 LICENSE。