ControlNet 的预处理器负责把参考图像转换成条件图,例如 Canny 边缘图、深度图或 OpenPose 骨架图。这部分计算与扩散模型本身相互独立,并且在参考图不变、参数不变的情况下,每次生成的输出完全一致。重复生成同一张图时,如果每次都重新执行预处理,就会浪费大量算力。启用预处理器缓存后,系统会在第一次运行时保存处理结果,后续任务直接读取缓存,从而显著加快二次生成速度。

下面从缓存解决的问题、不同界面的配置方法以及自定义实现三个角度展开说明。
预处理器缓存解决了什么问题
ControlNet 支持的预处理器种类很多,计算开销差异较大。像 Canny 边缘检测通常基于 OpenCV,单张图像耗时在几十毫秒级别,虽然很快,但在批量生成几百张图时累计开销不可忽视。深度估计使用的 MiDaS 模型需要一次小规模神经网络推理,512×512 图像通常需要一到两秒。而 OpenPose 姿态估计因为要检测人体关键点,耗时可能更高。如果每次生成都要重复这些操作,总出图时间会被明显拉长。
缓存的核心思路是记录预处理输入的指纹。输入包括参考图内容、预处理器类型以及所有相关参数,例如 Canny 的低阈值和高阈值。只要这些输入不变,就可以直接复用上一次的输出。缓存文件通常保存为 PNG 图像或 NPY 数组,存放在内存或磁盘中。第二次生成时,程序先计算同样的指纹,如果命中缓存,就跳过预处理步骤,把条件图直接送入 ControlNet。
实际测试中,假设深度预处理耗时 1.5 秒,连续调整提示词生成 20 张图,不启用缓存需要额外花费 30 秒。启用缓存后,只有第一次需要执行预处理,其余 19 次直接读取结果,节省的时间非常可观。对于需要反复试错或者批量输出的工作流,这个优化尤其有价值。
在 Stable Diffusion WebUI 中启用缓存
如果你使用的是基于 SD WebUI 的 ControlNet 扩展,可以在设置页面找到预处理缓存相关选项。进入 Settings,搜索 ControlNet,在展开的选项中查找 Cache preprocessor results 或类似字样的开关。不同扩展版本名称可能略有差异,但核心功能都是控制是否保存预处理结果。
启用后,WebUI 会根据参考图和预处理器参数生成缓存键。第一次使用某张参考图时,日志中会出现缓存写入的提示;再次使用相同图像和参数时,日志会显示缓存命中,预处理节点被跳过。此时控制台输出大致如下:
[ControlNet] preprocessor cache hit for canny [ControlNet] skip preprocessing, load cached result
缓存通常可以选择存放在内存或磁盘。内存缓存读取速度最快,但会占用系统 RAM,如果批量任务很多,可能导致内存紧张。磁盘缓存速度稍慢,但容量大,适合长期保留。建议根据机器配置选择:内存 32GB 以上可以优先考虑 RAM 缓存,否则使用磁盘缓存并定期清理。
在 ComfyUI 中减少重复预处理
ComfyUI 的节点式工作流让预处理和采样流程更加透明。默认情况下,Canny、Depth 等预处理节点每次运行工作流时都会重新执行。如果想避免重复计算,一个直接做法是把预处理结果保存为图像文件,后续工作流改用 LoadImage 节点加载该文件,替代实时的预处理器节点。
具体操作可以是:先连接一次预处理器节点,运行后得到条件图,使用 SaveImage 节点将其保存到输出目录。之后新建工作流或调整提示词时,将 ControlNet 的条件图输入改为 LoadImage 加载已保存的文件。这种方式相当于手动缓存,能够完全绕过预处理计算。缺点是参考图或参数变化时需要重新保存。
如果你希望自动管理缓存,也可以在 ComfyUI 的自定义节点中编写类似逻辑。通过计算输入图像和预处理参数的哈希值,将结果缓存到指定目录,并在节点执行前检查缓存。这种方式的实现思路与下一节的自定义脚本一致。
用 Python 实现预处理器缓存
在使用 diffusers 或 controlnet_aux 搭建自定义推理管线时,可以通过简单的函数装饰器实现缓存。下面是一个基于文件缓存的示例,它接收参考图路径和 Canny 参数,返回处理后的灰度图。第一次运行会调用 CannyDetector 并保存结果,后续相同输入直接读取缓存文件。
import os
import hashlib
from PIL import Image
from controlnet_aux import CannyDetector
def get_cache_path(image_path, preprocessor, low_threshold, high_threshold):
key = f'{image_path}_{preprocessor}_{low_threshold}_{high_threshold}'
digest = hashlib.md5(key.encode('utf-8')).hexdigest()
return os.path.join('cache', 'controlnet', f'{digest}.png')
def preprocess_with_cache(image_path, low_threshold=100, high_threshold=200):
cache_path = get_cache_path(image_path, 'canny', low_threshold, high_threshold)
if os.path.exists(cache_path):
return Image.open(cache_path).convert('L')
image = Image.open(image_path)
detector = CannyDetector()
result = detector(image, low_threshold, high_threshold)
os.makedirs(os.path.dirname(cache_path), exist_ok=True)
result.save(cache_path)
return result
缓存键基于输入图像路径和参数拼接后的哈希值。这样既能保证相同输入命中缓存,也能在参数改变时自动生成新的缓存文件。需要注意的是,如果参考图像文件被替换但路径不变,哈希不会变化,此时应把文件内容的哈希也纳入缓存键。更严谨的做法是读取图像字节并计算摘要,而不是只依赖路径。
这个示例将缓存保存为 PNG,优点是方便预览和调试,但读写速度不如 NPY 或 pickle。如果对性能要求更高,可以保存为 numpy 数组并使用 np.load 和 np.save。另外,缓存目录需要提前创建,示例中通过 exist_ok=True 自动处理。
缓存使用注意事项与最佳实践
启用缓存后,磁盘占用会随着参考图数量增长。建议为每个项目建立独立的缓存目录,例如 cache/controlnet/project_a,并定期清理超过两周未使用的缓存文件。如果使用 RAM 缓存,要留意内存水位,避免长时间批量任务导致系统交换。对于 Canny 这类轻量预处理器,缓存收益相对有限,但如果任务量很大,仍然值得开启。对于 Depth 和 OpenPose 等较重的预处理器,缓存带来的加速非常明显。
缓存失效是一个容易忽略的问题。当升级预处理器模型版本、更改预处理器的内部实现或调整参数时,旧的缓存结果可能不再正确。此时需要手动清空缓存目录或让程序在版本变化时生成新的缓存前缀。可以通过在缓存键中加入模型版本号来避免混淆。
最佳实践是结合具体工作流评估缓存收益。先用一小批样本测量单次预处理的平均耗时,再决定是否开启缓存以及使用哪种存储介质。对于参考图长期固定的批量出图场景,缓存能显著减少等待时间;而对于参考图频繁更换的场景,缓存的命中率较低,维护成本反而可能超过收益。
ControlNet预处理器缓存Stable Diffusion修改时间:2026-09-23 14:36:37