一、对比背景:为什么缓存策略值得专门比较
围绕节点式 AI 绘图框架,大家讨论最多的通常是节点用法、工作流编排和算力配置,真正深入看缓存细节的人其实不多。但如果把“学习这类框架的正确路线”拆开来看,节点和工作流只是入门,缓存机制才是进阶绕不开的一环。原因在于:只要你开始在一台机器上同时堆叠多个模型、多个 LoRA、多套工作流,缓存策略就会直接决定三件事:
- 你是在浪费算力,还是把显存和内存用在刀刃上;
- 容器会不会莫名其妙地 OOM(内存溢出);
- 工作流切换时,是“秒级热身”还是“从头再来”。
本文将框架当前主流的缓存架构与四类策略——CLASSIC(经典)、LRU(最近最少使用)、RAM_PRESSURE(内存压力感知)、NONE(无缓存)——放在一起,围绕“键怎么算、什么时候命中、什么时候过期”三个核心问题做系统对比,并给出多模型、多 LoRA、多工作流场景下的选型建议。
二、对象定义:两路缓存与四种策略
2.1 两类缓存:outputs 与 objects
执行一个工作流时,框架内部维护两类缓存:
- outputs 缓存:存放节点的中间结果与 UI 输出。命中时可以直接跳过节点执行,这是真正省计算、省时间的地方。
- objects 缓存:存放节点对象实例(类实例),避免每次执行都重新构造节点对象。它省的是对象初始化开销,规模和收益都远小于前者。
两类缓存由一个统一的缓存集合类管理。策略的定义与选择逻辑可以用示意代码理解:
# 缓存策略枚举(示意)class CacheType(Enum): CLASSIC = 0 # 经典策略:尽量保留所有输出 LRU = 1 # 最近最少使用:按条目数设上限 NONE = 2 # 完全关闭缓存 RAM_PRESSURE = 3 # 内存压力感知:按内存水位设上限
2.2 四种策略与选择优先级
从启动参数到缓存策略的选择,大致遵循这样的优先级:
- 显式指定 LRU 相关参数 → 使用 LRU 策略;
- 否则指定内存阈值相关参数 → 使用 RAM_PRESSURE 策略;
- 否则指定关闭缓存参数 → 使用 NONE 策略;
- 都未指定 → 默认使用 CLASSIC 策略。
需要特别强调:不同策略只改变 outputs 缓存的实现方式,而 objects 缓存基本始终采用层级键结构(按节点 ID 与节点类型组织)。这意味着切换策略主要影响“中间结果能不能被复用”,而不是节点对象的构造行为。
三、相同点分析
四种策略行为差异明显,但底层逻辑多处一致:
- 目标一致:都是为了跳过“输入没有变化的节点”的重复执行,缩短二次运行时间。
- 键的思路一致:outputs 缓存统一按输入签名做键,本质是“内容寻址”的变化指纹——由节点类型、输入参数与上游输出指纹共同决定;objects 缓存统一按(节点 ID,节点类型)做键。策略差异不体现在键的计算上,而体现在“命中后保留多久、何时被清理”。
- 级联特性一致:上游任何一个参数(如随机种子)变化,都会让下游所有节点的签名连锁失效,这一点与选择哪种策略无关。
- 配置方式一致:均通过启动参数选择,不影响工作流本身的定义与语义。
# outputs 缓存键(示意)def outputs_key(node): return hash( node.class_type, # 节点类型 node.inputs, # 输入参数与连线关系 upstream_keys(node) # 上游输出签名指纹(级联传播) )# objects 缓存键(示意)def objects_key(node): return (node.node_id, node.class_type)
四、核心差异分析
4.1 什么时候命中
- CLASSIC:只要节点签名匹配、且条目仍在缓存中,即命中。同一工作流反复迭代调参时命中率最高。
- LRU:签名匹配之外,还要求条目处于“最近使用的前 N 项”保留窗口内,被挤出窗口的历史条目无法命中。
- RAM_PRESSURE:签名匹配之外,还要求条目未被内存压力清理掉。当前执行路径上的输出通常优先保留,不在路径上的历史分支更容易被淘汰。
- NONE:outputs 永不命中,每次执行都完整重算(objects 缓存仍按默认层级结构工作)。
4.2 什么时候过期
- CLASSIC:主动保留、被动清理。签名变化、工作流结构变化、被其他工作流条目挤占或内存不足时才清理。
- LRU:超过容量上限即清理,最久未使用的条目先淘汰,行为可预期。
- RAM_PRESSURE:以系统内存水位为界。内存占用超过设定阈值(常见实现中默认约 16GB,实际以所用版本为准)时,清理不在当前执行路径上的输出。
- NONE:每次执行结束后即丢弃,不存在“过期”概念。
4.3 资源占用与 OOM 风险
- CLASSIC:内存占用最高且难以精确预测,旧输出可能不断累积,是容器环境中 OOM 风险最高的策略。
- LRU:占用上限由条目数决定,可以预算,但不同节点输出的内存差异较大,设定时需留余量。
- RAM_PRESSURE:占用上限由内存水位决定,自适应性强,但清理动作本身有开销,水位逼近阈值时可能出现清理抖动。
- NONE:占用最低,但重复计算最多,吞吐最差。
4.4 运维与配置复杂度
- CLASSIC 与 NONE 零配置,开箱即用;
- LRU 需要估计“常用工作流的工作集规模”来设定容量;
- RAM_PRESSURE 需要结合机器总内存、模型加载占用和框架自身开销设定阈值,并依赖内存监控验证效果。
五、对比表格
| 对比维度 |
CLASSIC |
LRU |
RAM_PRESSURE |
NONE |
| 核心思路 |
尽量保留所有输出 |
只保留最近使用的 N 项 |
内存超阈值时清理非当前路径输出 |
不保留任何输出 |
| 命中条件 |
签名匹配且条目未被清理 |
签名匹配且在保留窗口内 |
签名匹配且未被水位清理 |
永不命中 |
| 过期时机 |
签名变化、内存不足或被挤占 |
超容量上限,最久未用先淘汰 |
内存超过设定阈值 |
执行后即弃 |
| 内存占用 |
最高且不可预测 |
有确定上限(按条目数) |
有上限(按内存水位) |
最低 |
| 重复计算 |
最少(内存充足时) |
取决于容量与轮换频率 |
取决于阈值与并发 |
每次全量重算 |
| 配置成本 |
零配置 |
需估计工作集大小 |
需设定合理阈值 |
零配置 |
| 容器 OOM 风险 |
最高 |
较低 |
较低(依赖阈值设定) |
最低 |
| 典型场景 |
单工作流反复迭代 |
少量工作流固定轮换 |
多模型多 LoRA 共享机器 |
调试与回归验证 |
六、典型场景选择
- 单工作流反复调参迭代 → CLASSIC。此时命中率最关键,内存通常也够用。
- 固定几套工作流轮换使用 → LRU。把容量设为“常用工作流热路径的条目规模”,即可在轮换时保持秒级热身。
- 多模型、多 LoRA、多人共用机器或容器化部署 → RAM_PRESSURE。以内存水位兜底,避免 OOM 优先于追求命中率。
- 调试定位、回归验证、内存极端受限环境 → NONE。保证每次执行行为一致,便于排查问题。
七、选型建议
- 内存充裕、场景单一:默认 CLASSIC 即可,无需额外配置。
- 工作流数量可控且经常轮换:优先 LRU,容量宁大勿小,但需监控实际内存曲线。
- 多租户、容器化、内存敏感:优先 RAM_PRESSURE,阈值需为模型加载与系统进程预留余量。
- 需要确定性输出或排查问题:短期切到 NONE,定位完成后再切回。
- 由于 objects 缓存不受策略影响,切换策略的收益主要体现在 outputs 上;若瓶颈在节点对象构造(较少见),调整策略帮助有限。
无论部署在本地工作站,还是百度智能云等云平台的 GPU 实例上,四种策略的取舍逻辑是一致的;在按量计费的云上环境中,减少重复计算还直接关系到资源成本,值得多花时间做基准测试。
八、迁移与使用注意事项
- 切换只改启动参数,工作流无需改动;但命中行为变化会改变执行时间分布,切换后应重新做基准测试。
- 签名级联意味着:上游一个种子或参数变化会使下游全部签名失效,任何策略都救不了“整条链重算”,策略只影响“没变的分支”能省多少。
- 容器环境应同时设置容器内存限制与缓存参数(LRU 容量或内存阈值),并为模型加载峰值预留余量。
- LRU 容量过小会退化到接近无缓存的效果;过大则接近 CLASSIC 的内存风险,建议从小到大逐步调整并观察命中率。
- RAM_PRESSURE 的清理时机依赖内存水位,瞬时并发或多任务叠加可能造成清理抖动,建议结合内存监控与告警使用。
- 升级框架版本时,缓存实现细节可能调整,建议在测试环境复验策略行为后再上线。
九、总结
四种缓存策略的本质差异可概括为一句话:CLASSIC 用内存换命中率,NONE 用计算换内存,LRU 与 RAM_PRESSURE 是两种不同的“中间态”——前者按条目数设上限,后者按内存水位设上限。
决策思路建议三步走:先看内存预算(能否承受 CLASSIC 的占用),再看工作流复用模式(单工作流深耕还是多工作流轮换),最后看运维能力(能否有效监控内存与命中率)。据此对号入座,就能在算力利用率、内存安全与迭代效率之间,找到适合自己场景的平衡点。