ZML_多线程子工作流
Run a sub-workflow 64 threads at a time — ComfyUI's threading workaround
- 变量包
- 图像列表
- 任意数据列表
- 执行状态
ComfyUI has a famous weakness: a graph executes node-by-node in a single pass, so "do this same cheap thing 300 times" runs sequentially and takes forever. ZML_ParallelJsonContainer is the author's workaround - paste a workflow (in API JSON form) into the node, tell it how many times to run and on how many threads, and it fans the jobs out across a ThreadPoolExecutor. The README names the motivation outright: it exists "to solve ComfyUI's inability to multithread," with examples like running 32 threads to watermark images in parallel, or 8 threads calling an image API concurrently.
Before you get excited about parallelizing your FLUX sampler: read the limits. The node runs sub-workflow nodes directly in worker threads, so it only works for API-call nodes and pure-logic nodes - anything that loads models or touches CUDA concurrently will thrash or crash. The README says this explicitly. It's a concurrency tool for the parts of your workflow that are I/O-bound or trivial, not for GPU sampling. For the API-call case it's genuinely great - your 300 image-API requests go out in parallel instead of one at a time.
How it works
- JSON工作流 - paste a workflow in ComfyUI's API format. You get this from a workflow JSON by stripping the UI layout (or from the "Export (API)" menu). Placeholders like
{{变量名}}get substituted per-run. - 执行次数 (1–3000) - how many times to run it.
- 并行线程数 (1–64) - max concurrent threads.
- 执行完成后清理缓存 (default on) - after all jobs, run the full cleanup (unload models, gc, empty CUDA cache). The tooltip adds a useful warning: keep it on, especially if you disable returning images, or the cleanup can be ineffective.
- 返回图像 (开启/关闭) - whether to collect output images. Disable it when you only want the side effects, which saves a lot of memory.
- 控制台日志 - per-task success lines on/off.
- 变量包 (optional
VAR_BUNDLE) - the variable definitions. A bundle maps a placeholder name to a variable type:list(cycle values by index),math_int/math_float(start + index·step), orseed(fixed / incrementing / random). You build these with the pack'sZML_ParallelVariable*nodes - e.g.ZML_ParallelVariableAnyfor arbitrary values.
The sub-workflow marks its results with two special sink nodes: ZML_SubflowExportImage (emits the output image) and ZML_SubflowExportAny (emits arbitrary data). The container watches for those classes and collects their outputs.
Outputs: 图像列表 (list of images), 任意数据列表 (list of strings), and 执行状态 (per-task status lines, ✅/❌).
When it shines and when it doesn't
Shines: parallel HTTP calls (this is the big one - the README's "8 threads calling the banana API" example), batch watermarking, anything that's waiting on I/O. Doesn't shine: GPU sampling, model loading, video decoding - anything that would collide in a thread pool. If your "sub-workflow" contains a KSampler, don't.
Install
Ships in ComfyUI-ZML-Image:
cd ComfyUI/custom_nodes
git clone https://github.com/zml-w/ComfyUI-ZML-Image
# restart ComfyUI
or ComfyUI Manager → search "ComfyUI-ZML-Image". No extra dependencies (uses Python's concurrent.futures). Chinese-first UI - 执行次数 is "execution count," 并行线程数 is "parallel threads" - translation patch at https://github.com/zml-w/ZZZ_ZML_English_Patch.
The catch that trips people: threads are Python threads, so compute-bound sub-workflow code gets no speedup (GIL), and the 执行完成后清理缓存 warning about returning images is worth reading twice - the tooltip is the author flagging a real memory leak path, not ceremony.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| JSON工作流 | STRING | — | |
| 执行次数 | INT | 11–3000 | — |
| 并行线程数 | INT | 11–64 | — |
| 执行完成后清理缓存 | BOOLEAN | true | 所有任务执行完成后执行全面清理,包括卸载模型、Python垃圾回收、CUDA缓存释放,以减少工作流执行造成的内存显存残留。尽量在关闭返回图像时开启这个功能,不然可能会失效。 |
| 返回图像 | COMBO | 开启 | 2 options: 开启, 关闭 |
| 控制台日志 | COMBO | 开启 | 2 options: 开启, 关闭 |
| 变量包opt | VAR_BUNDLE | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| 图像列表 | IMAGE | — |
| 任意数据列表 | STRING | — |
| 执行状态 | STRING | — |