<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>潘锦的空间 &#187; AI架构</title>
	<atom:link href="https://www.phppan.com/tag/ai%e6%9e%b6%e6%9e%84/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.phppan.com</link>
	<description>SaaS SaaS架构 团队管理 技术管理 技术架构 PHP 内核 扩展 项目管理</description>
	<lastBuildDate>Sun, 10 May 2026 02:26:45 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=3.9.40</generator>
	<item>
		<title>对最近 AI 落地工程实践的一些想法和思考</title>
		<link>https://www.phppan.com/2026/04/some-thoughts-and-reflections-on-recent-ai-implemented-engineering-practices/</link>
		<comments>https://www.phppan.com/2026/04/some-thoughts-and-reflections-on-recent-ai-implemented-engineering-practices/#comments</comments>
		<pubDate>Sat, 25 Apr 2026 00:56:17 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[Agent]]></category>
		<category><![CDATA[AI幻觉]]></category>
		<category><![CDATA[AI架构]]></category>
		<category><![CDATA[RAG]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2493</guid>
		<description><![CDATA[最近和小区某上市公司的 CFO 喝茶聊 AI，在过程中思维和实际场景的碰撞，记录如下： 穿透复杂的表象，当前  [&#8230;]]]></description>
				<content:encoded><![CDATA[<p style="color: #424b5d;" data-tool="mdnice编辑器">最近和小区某上市公司的 CFO 喝茶聊 AI，在过程中思维和实际场景的碰撞，记录如下：</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">穿透复杂的表象，当前 LLM 的底层运行逻辑其实非常单一：它本质上是一个自回归的序列生成器，根据已有的上下文，计算词表中每一个 token 出现的概率分布，然后从中采样出下一个 token。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">但这里的「概率」绝非毫无逻辑的随机掷骰子。 这种概率分布，是模型在海量预训练数据中内化的语言规律、世界知识以及逻辑推理能力的数学投影。通过多层 Transformer 网络与注意力机制（Attention），模型在极高的维度上完成了对上下文语义的深度解析与特征关联，从而将符合人类逻辑、契合当前语境的 token 赋予极高的概率权重。它是在用统计学的方式，重现人类的逻辑推理过程。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">然而，无论其内部的概率计算多么精密，从软件工程的宏观视角来看，我们本质上依然是在传统的确定性系统中，强行引入了一个基于概率采样的非确定性组件。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">传统软件工程建立在严格的确定性之上。输入特定的参数，经过固定的业务逻辑，必然得到预期的输出。现在我们将核心逻辑交由概率模型处理，相同的输入在不同的时间点，可能会产生完全不同的输出路径。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">幻觉无法被根除。它是自回归模型的内生特性，是概率采样的必然产物。我们在进行系统架构设计时，必须将幻觉视为系统的常态。试图通过修改 Prompt 来彻底消除幻觉，在工程上徒劳无功。我们需要在系统边界处建立起拦截机制，用确定性的规则去兜底概率模型的不确定性。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #e7642b;">容错度决定落地</span></h1>
<p style="color: #424b5d;" data-tool="mdnice编辑器">当前商业化落地最顺畅、ROI 最高的场景，全部集中在高容错度领域。写行业报告、生成营销文案、文生图、视频生成、游戏 NPC 对话。这类场景的核心特征在于缺乏绝对的客观标准。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">在内容创作领域，模型偶尔的逻辑发散会被用户视为创造力。工程团队不需要在接口的绝对可用性和输出的绝对准确性上死磕，只需要保证底线的内容安全和合理的响应延迟。系统可用性达到 95% 就能让用户产生极强的获得感。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">一旦进入低容错度场景，工程实现的复杂度会呈指数级上升。医疗诊断、工业控制、核心交易链路。在这些领域，0.1% 的幻觉率都会导致灾难性的业务后果。我们在评估一个 AI 项目是否立项时，首要考量指标就是业务场景的容错底线。容错度越低，外围需要的确定性校验代码就越厚重，最终会导致系统的维护成本远超 AI 带来的效率提升。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #e7642b;">知识外挂 RAG</span></h1>
<p style="color: #424b5d;" data-tool="mdnice编辑器">RAG 的出现是为了解决模型内部知识更新滞后和私有数据隔离的问题。其核心原理是将外部文档切片、向量化，在用户提问时检索相关切片，拼接到 Prompt 中作为上下文喂给大模型。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">在实际的工程环境里，RAG 的核心瓶颈在检索链路。切片策略直接决定了召回质量。按固定 token 长度切分会破坏语义完整性，导致关键信息被腰斩。按标点符号或段落切分会导致切片长度方差过大，影响向量化模型的表达能力。我们在生产环境中通常需要针对不同格式的文档编写定制化的解析器，将 PDF 或 Word 还原为结构化的文档树，再基于文档树的层级进行语义切片。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">单一的向量检索在面对专有名词和长尾词汇时表现极差。我们必须采用混合检索架构：稠密向量检索加上稀疏词表检索。向量检索负责语义泛化，处理同义词和模糊表达。词表检索负责精准匹配产品型号、人名和内部项目代号。混合检索引入了多路召回合并的问题，通常需要引入倒数秩融合算法来重排结果。系统复杂度和查询延迟会成倍增加。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">数据清洗占据了 RAG 项目 80% 的研发精力。直接将企业内部的原始文档灌入向量数据库，最终的问答准确率通常不到 40%。文档中存在大量的废话、过期的流程规范以及相互冲突的条款。垃圾进，垃圾出。我们在构建知识库之前，必须通过脚本和人工介入，对语料进行严格的去重、降噪和结构化提取。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #e7642b;">工具调用确定性</span></h1>
<p style="color: #424b5d;" data-tool="mdnice编辑器">为了弥补概率模型的缺陷，我们需要引入确定性的工具。Function Calling 机制本质上是给 LLM 接上双手。模型负责理解自然语言意图并提取结构化参数，具体的业务逻辑交由传统的确定性脚本执行。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器"><strong>工具调用的工程难点在于参数提取的稳定性</strong>。当注册的工具数量超过十个，或者参数结构嵌套层级过深时，模型的输出格式极易崩溃。我们在中间层必须加入严格的 Schema 校验机制。一旦校验失败，需要截断错误信息并触发重试。重试次数上限通常设定为 3 次，继续增加会耗尽上下文窗口并导致请求超时。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">多轮工具调用会带来严重的延迟问题。模型每决定调用一次工具，都需要经历一次完整的网络请求和推理过程。如果一个复杂任务需要串行调用三个工具，用户的等待时间会轻易突破 10 秒。我们在架构设计时，需要尽可能将细粒度的 API 聚合成粗粒度的宏接口，<strong>减少模型与业务系统的交互频次</strong>。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #e7642b;">Agent 架构的脆弱性与状态管理</span></h1>
<p style="color: #424b5d;" data-tool="mdnice编辑器">多智能体（Multi-Agent）架构在技术社区被过度神话。多个大模型相互协作、自主规划任务的 Demo 看起来非常惊艳。在真实的工业场景中，完全由 LLM 自主驱动的 Agent 链路极其脆弱。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">误差会在多步推理中被迅速放大。假设单个 Agent 节点的输出准确率为 90%，一个包含五个节点的串行任务，最终的成功率会暴跌至 59%。任何一个节点的幻觉都会导致后续链路彻底跑偏。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">我们在生产环境中构建复杂任务流时，坚决摒弃由 LLM 自主决定执行路径的黑盒模式。控制流必须由传统的有向无环图（DAG）或状态机来接管。LLM 仅仅作为状态机中的一个计算节点，负责处理非结构化数据的理解和生成。节点与节点之间的状态流转、条件判断、异常重试，全部由确定性的代码实现。这种设计牺牲了系统的灵活性，换取了业务系统必须具备的稳定性和可观测性。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #e7642b;">非确定性系统的测试与监控</span></h1>
<p style="color: #424b5d;" data-tool="mdnice编辑器">非确定性系统的测试与监控，是传统软件工程团队转型 AI 开发时遇到的最大痛点。传统的单元测试基于断言，期望输出是固定的字符串或数值。面对 LLM 每次都不一样的回答，基于精确匹配的 CI/CD 流水线会全线崩溃。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">我们重构了整个测试评估体系。引入 LLM-as-a-Judge 机制，使用一个能力更强、参数规模更大的模型来评估业务模型的输出质量。评估维度被拆解为相关性、事实一致性、格式合规性等具体指标。在每次模型版本迭代或 Prompt 修改后，必须在包含上千个真实业务 Case 的黄金数据集上运行自动化评估。只有各项指标的波动在可控范围内，才能进行灰度发布。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">在监控层面，传统的 APM 工具无法满足需求。我们需要采集每一个请求的 Prompt 模板版本、输入变量、输出结果、Token 消耗量以及推理延迟。这些数据是后续进行 Bad Case 分析和模型微调的唯一原料。针对 Token 消耗的监控直接与业务成本挂钩。我们会在网关层设置严格的并发限制和预算熔断机制，防止恶意请求或死循环调用导致账单失控。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #e7642b;">两种范式的碰撞</span></h1>
<p style="color: #424b5d;" data-tool="mdnice编辑器">AI First 与 AI 辅助是完全不同的架构逻辑。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">AI 辅助是在现有系统中打补丁。主干流程依然是传统的表单和按钮，AI 作为一个侧边栏或悬浮窗存在，提供总结、翻译、润色功能。开发成本极低，对原有系统无侵入。用户在遇到问题时，可以选择性地向 AI 求助。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">AI First 要求重构整个交互形态和底层流转逻辑。系统不再依赖预设的菜单树，由 LLM 充当中央路由。用户的自然语言输入直接驱动底层状态机流转。这要求所有内部 API 具备极高的自描述能力，业务逻辑必须高度解耦。我们在推进 AI First 架构时，面临的最大阻力通常来自老旧系统的技术债。历史遗留的紧耦合代码根本无法被封装成独立的工具供模型调用。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #e7642b;">财务场景的拆解</span></h1>
<p style="color: #424b5d;" data-tool="mdnice编辑器">财务场景是典型的低容错度、高确定性要求的领域。将概率模型直接应用于财务核心链路会引发严重的合规风险。可落地的切入点集中在外围的非结构化数据处理和信息流转环节。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">发票与报销单据的信息抽取是一个高价值场景。传统 OCR 结合正则匹配在面对版式多变的票据时维护成本极高。引入大模型进行多模态信息抽取，将非结构化的图片或 PDF 转换为结构化的 JSON 数据。抽取后的数据必须经过传统规则引擎的二次校验，例如金额试算平衡验证、税号合规性检查。模型在这里承担的是「粗加工」角色，最终的业务落库动作依然由确定性代码把控。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">财务制度问答可以大幅降低沟通成本。基于企业内部报销规范构建 RAG 系统。员工在提单前通过自然语言查询报销标准。这里的 RAG 必须严格限制模型的发散，Prompt 中需强制要求「仅根据检索到的内容回答，未提及的内容直接回复不知道」。为了防止模型编造财务政策，我们会在输出层增加一层文本相似度校验，确保模型的回答与检索到的原文保持高度一致。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">财务分析报告初稿生成也是一个可行的方向。将结构化的财务报表数据通过代码转换为文本描述，作为上下文喂给模型，让其生成趋势分析和异常波动提示。模型在这里仅作为「翻译官」和「排版员」，不参与任何数值计算。所有的同比、环比计算必须在传统代码层完成，将计算结果以明确的数值形式提供给模型。让 LLM 去做算术题是工程上的反模式。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">数据隐私在财务场景中是不可逾越的红线。公有云 API 无法满足审计要求。我们通常需要采用本地私有化部署的开源模型。7B 到 14B 参数规模的模型经过量化处理后，可以在单张消费级显卡上流畅运行。通过针对财务语料的微调，这些小模型在特定信息抽取任务上的表现可以持平甚至超越千亿参数的通用大模型。私有化部署带来了硬件采购和模型运维的额外成本，需要在项目初期进行严格的 ROI 测算。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">以上</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/04/some-thoughts-and-reflections-on-recent-ai-implemented-engineering-practices/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ComfyUI 的缓存架构和实现</title>
		<link>https://www.phppan.com/2025/11/comfyui-cache/</link>
		<comments>https://www.phppan.com/2025/11/comfyui-cache/#comments</comments>
		<pubDate>Sat, 29 Nov 2025 01:27:49 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[AI架构]]></category>
		<category><![CDATA[comfyUI]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2439</guid>
		<description><![CDATA[围绕 ComfyUI，大家讨论最多的是节点、工作流、算力这些，真正去看缓存细节的人其实不多。但只要你开始在一台 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" style="color: #000000;" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">围绕 ComfyUI，大家讨论最多的是节点、工作流、算力这些，真正去看缓存细节的人其实不多。但只要你开始在一台机器上堆多个模型、多个 LoRA、多个 workflow，缓存策略就会直接决定这几件事：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">你是算力浪费，还是把显存 / 内存用在刀刃上；</section>
</li>
<li>
<section style="color: #010101;">容器会不会莫名其妙 OOM；</section>
</li>
<li>
<section style="color: #010101;">工作流切换时，是“秒级热身”还是“从头再来”。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这篇文章只做一件事：把 ComfyUI 当前的缓存架构和实现讲清楚，重点是三类策略（CLASSIC / LRU / RAM_PRESSURE）在「键怎么算、什么时候命中、什么时候过期」上的差异，以及在多模型、多 LoRA、多 workflow 场景下应该怎么选择。</p>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">1. 整体架构：两路缓存 + 四种策略</span></h1>
<p data-tool="mdnice编辑器">先把整体结构捋清楚。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">1.1 两类缓存：outputs 和 objects</span></h2>
<p data-tool="mdnice编辑器">在执行一个工作流的时候，ComfyUI 维护了两类缓存：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">outputs</code>：<br />
存中间结果和 UI 输出。命中时可以直接跳过节点执行，这部分是真正省计算的地方。</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">objects</code>：<br />
存节点对象实例（类实例），避免每次重新构造节点对象。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这两类缓存由一个统一的集合类 <code style="color: #0e8aeb;">CacheSet</code> 来管理。核心结构在 <code style="color: #0e8aeb;">execution.py</code> 中：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-class"><span class="hljs-keyword" style="color: #c678dd;">class</span> <span class="hljs-title" style="color: #e6c07b;">CacheType</span><span class="hljs-params">(Enum)</span>:</span>
    CLASSIC = <span class="hljs-number" style="color: #d19a66;">0</span>
    LRU = <span class="hljs-number" style="color: #d19a66;">1</span>
    NONE = <span class="hljs-number" style="color: #d19a66;">2</span>
    RAM_PRESSURE = <span class="hljs-number" style="color: #d19a66;">3</span>

<span class="hljs-class"><span class="hljs-keyword" style="color: #c678dd;">class</span> <span class="hljs-title" style="color: #e6c07b;">CacheSet</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">__init__</span><span class="hljs-params">(self, cache_type=None, cache_args={})</span>:</span>
        <span class="hljs-keyword" style="color: #c678dd;">if</span> cache_type == CacheType.NONE:
            self.init_null_cache()
        <span class="hljs-keyword" style="color: #c678dd;">elif</span> cache_type == CacheType.RAM_PRESSURE:
            cache_ram = cache_args.get(<span class="hljs-string" style="color: #98c379;">"ram"</span>, <span class="hljs-number" style="color: #d19a66;">16.0</span>)
            self.init_ram_cache(cache_ram)
        <span class="hljs-keyword" style="color: #c678dd;">elif</span> cache_type == CacheType.LRU:
            cache_size = cache_args.get(<span class="hljs-string" style="color: #98c379;">"lru"</span>, <span class="hljs-number" style="color: #d19a66;">0</span>)
            self.init_lru_cache(cache_size)
        <span class="hljs-keyword" style="color: #c678dd;">else</span>:
            self.init_classic_cache()
        self.all = [self.outputs, self.objects]
</code></pre>
<p data-tool="mdnice编辑器">不管是哪种策略，结构都是：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">outputs</code>：<strong style="color: #0e88eb;">按输入签名做 key</strong>；</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">objects</code>：<strong style="color: #0e88eb;">按 (node_id, class_type) 做 key</strong>。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">1.2 四种策略：CLASSIC / LRU / RAM_PRESSURE / NONE</span></h2>
<p data-tool="mdnice编辑器">从启动参数到缓存策略的选择，大致是这样的优先级（在 <code style="color: #0e8aeb;">main.py</code> 中）：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">指定 <code style="color: #0e8aeb;">--cache-lru</code> → 用 LRU；</section>
</li>
<li>
<section style="color: #010101;">否则指定 <code style="color: #0e8aeb;">--cache-ram</code> → 用 RAM_PRESSURE；</section>
</li>
<li>
<section style="color: #010101;">否则指定 <code style="color: #0e8aeb;">--cache-none</code> → 完全关闭缓存；</section>
</li>
<li>
<section style="color: #010101;">都没指定 → 默认 CLASSIC。</section>
</li>
</ul>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">CacheType</code> 的定义前面已经贴了。不同策略只决定 <strong style="color: #0e88eb;">outputs 的实现方式</strong>，而 objects 基本始终使用层级缓存 <code style="color: #0e8aeb;">HierarchicalCache(CacheKeySetID)</code>，后面细讲。</p>
<hr data-tool="mdnice编辑器" />
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">2. 缓存键：输入签名 + 变化指纹</span></h1>
<p data-tool="mdnice编辑器">缓存是否命中，首先取决于“key 算得是否合理”。ComfyUI 的设计核心是：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">输出缓存（outputs）</strong>：用“输入签名 + is_changed 指纹（+ 某些情况下的 node_id）”作为 key；</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">对象缓存（objects）</strong>：用 <code style="color: #0e8aeb;">(node_id, class_type)</code> 作为 key。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">2.1 输出缓存：输入签名是怎么来的</span></h2>
<p data-tool="mdnice编辑器">输出键的生成由 <code style="color: #0e8aeb;">CacheKeySetInputSignature</code> 负责，核心逻辑在 <code style="color: #0e8aeb;">comfy_execution/caching.py:100-126</code>：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-keyword" style="color: #c678dd;">async</span> <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">get_node_signature</span><span class="hljs-params">(self, dynprompt, node_id)</span>:</span>
    signature = []
    ancestors, order_mapping = self.get_ordered_ancestry(dynprompt, node_id)
    signature.append(<span class="hljs-keyword" style="color: #c678dd;">await</span> self.get_immediate_node_signature(dynprompt, node_id,
    order_mapping))
    <span class="hljs-keyword" style="color: #c678dd;">for</span> ancestor_id <span class="hljs-keyword" style="color: #c678dd;">in</span> ancestors:
        signature.append(<span class="hljs-keyword" style="color: #c678dd;">await</span> self.get_immediate_node_signature(dynprompt,
        ancestor_id, order_mapping))
    <span class="hljs-keyword" style="color: #c678dd;">return</span> to_hashable(signature)
</code></pre>
<p data-tool="mdnice编辑器">这里有几个点：</p>
<ol data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">
<p style="color: #000000;"><strong style="color: #0e88eb;">不只看当前节点</strong>：<br />
它会按拓扑顺序把“当前节点 + 所有祖先”的签名拼在一起。这保证了只要整个子图的拓扑和输入一致，输出就能命中缓存。</p>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;"><strong style="color: #0e88eb;">immediate 签名包含什么</strong>：<br />
<code style="color: #0e8aeb;">get_immediate_node_signature</code> 会把这些信息打包进去（位置见同文件 108–126）：</p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">class_type</code>：节点类型；</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">is_changed</code> 指纹（通过 <code style="color: #0e8aeb;">fingerprint_inputs/IS_CHANGED</code>）；</section>
</li>
<li>
<section style="color: #010101;">必要时的 <code style="color: #0e8aeb;">node_id</code>；</section>
</li>
<li>
<section style="color: #010101;">有序的输入值/链接。</section>
</li>
</ul>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;"><strong style="color: #0e88eb;">什么时候把 node_id 也算进 key</strong>：<br />
当节点被声明为非幂等，或者内部有 <code style="color: #0e8aeb;">UNIQUE_ID</code> 这种隐含输入时，会把 <code style="color: #0e8aeb;">node_id</code> 加进签名（见 <code style="color: #0e8aeb;">comfy_execution/caching.py:18-23</code>, <code style="color: #0e8aeb;">116</code>），避免“看起来一样”的节点被错误复用。</p>
</section>
</li>
</ol>
<p data-tool="mdnice编辑器">最后通过 <code style="color: #0e8aeb;">to_hashable</code> 转成可哈希结构（<code style="color: #0e8aeb;">tuple/frozenset</code> 等），作为最终键值。</p>
<p data-tool="mdnice编辑器">结果是：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">输入完全相同 → key 相同 → 直接命中；</section>
</li>
<li>
<section style="color: #010101;">任意一个上游节点输入或参数变化 → 指纹变了 → key 不同 → 不会复用旧结果。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">2.2 对象缓存：用 (node_id, class_type)</span></h2>
<p data-tool="mdnice编辑器">节点对象的键由 <code style="color: #0e8aeb;">CacheKeySetID</code> 构造（<code style="color: #0e8aeb;">comfy_execution/caching.py:66-80</code>），逻辑简单：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">key = <code style="color: #0e8aeb;">(node_id, class_type)</code>。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">读取时：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">obj = caches.objects.get(unique_id)
<span class="hljs-keyword" style="color: #c678dd;">if</span> obj <span class="hljs-keyword" style="color: #c678dd;">is</span> <span class="hljs-literal" style="color: #56b6c2;">None</span>:
    obj = class_def()
    caches.objects.set(unique_id, obj)
</code></pre>
<p data-tool="mdnice编辑器">对象缓存存在的目的只有一个：同一个 workflow 执行过程中，不要重复 new 节点实例。</p>
<hr data-tool="mdnice编辑器" />
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">3. 缓存容器：Basic / Hierarchical / LRU / RAM / Null</span></h1>
<p data-tool="mdnice编辑器">有了 key，还需要一个合理的「容器」和「驱逐策略」。</p>
<p data-tool="mdnice编辑器">主要类在 <code style="color: #0e8aeb;">comfy_execution/caching.py</code>：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">BasicCache</code>：基础容器，提供 <code style="color: #0e8aeb;">set_prompt / clean_unused / get / set</code> 等；</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">HierarchicalCache</code>：按“父节点 → 子图”构建层级缓存；</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">LRUCache</code>：在 Basic + Hierarchical 的基础上增加代际 LRU；</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">RAMPressureCache</code>：在 LRU 的基础上增加 RAM 压力驱逐；</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">NullCache</code>：空实现（禁用缓存）。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">核心接口统一在 <code style="color: #0e8aeb;">BasicCache</code>：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">clean_unused</span><span class="hljs-params">(self)</span>:</span>
    self._clean_cache()
    self._clean_subcaches()
</code></pre>
<p data-tool="mdnice编辑器">层级相关逻辑在 <code style="color: #0e8aeb;">HierarchicalCache</code>，用来支持“子图单独分区缓存”。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">3.1 层级缓存：怎么定位到某个节点的分区</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">HierarchicalCache</code> 通过 parent 链定位子缓存，代码在 <code style="color: #0e8aeb;">comfy_execution/caching.py:242-269</code>：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">_get_cache_for</span><span class="hljs-params">(self, node_id)</span>:</span>
    parent_id = self.dynprompt.get_parent_node_id(node_id)
    ...
    <span class="hljs-keyword" style="color: #c678dd;">for</span> parent_id <span class="hljs-keyword" style="color: #c678dd;">in</span> reversed(hierarchy):
        cache = cache._get_subcache(parent_id)
        <span class="hljs-keyword" style="color: #c678dd;">if</span> cache <span class="hljs-keyword" style="color: #c678dd;">is</span> <span class="hljs-literal" style="color: #56b6c2;">None</span>: <span class="hljs-keyword" style="color: #c678dd;">return</span> <span class="hljs-literal" style="color: #56b6c2;">None</span>
    <span class="hljs-keyword" style="color: #c678dd;">return</span> cache

<span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">get</span><span class="hljs-params">(self, node_id)</span>:</span>
    cache = self._get_cache_for(node_id)
    <span class="hljs-keyword" style="color: #c678dd;">if</span> cache <span class="hljs-keyword" style="color: #c678dd;">is</span> <span class="hljs-literal" style="color: #56b6c2;">None</span>: <span class="hljs-keyword" style="color: #c678dd;">return</span> <span class="hljs-literal" style="color: #56b6c2;">None</span>
    <span class="hljs-keyword" style="color: #c678dd;">return</span> cache._get_immediate(node_id)

<span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">set</span><span class="hljs-params">(self, node_id, value)</span>:</span>
    cache = self._get_cache_for(node_id)
    <span class="hljs-keyword" style="color: #c678dd;">assert</span> cache <span class="hljs-keyword" style="color: #c678dd;">is</span> <span class="hljs-keyword" style="color: #c678dd;">not</span> <span class="hljs-literal" style="color: #56b6c2;">None</span>
    cache._set_immediate(node_id, value)
</code></pre>
<p data-tool="mdnice编辑器">含义很直接：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">根节点存在当前 cache；</section>
</li>
<li>
<section style="color: #010101;">某些节点生成子图，会在该节点下挂一个子 cache；</section>
</li>
<li>
<section style="color: #010101;">读写时，先通过 parent 链找到对应的子 cache 再读写。</section>
</li>
</ul>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">clean_unused()</code> 里除了清除不用的 key，还会删除没用到的子 cache 分区（<code style="color: #0e8aeb;">_clean_subcaches()</code>）。</p>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">4. 执行循环中的使用路径</span></h1>
<p data-tool="mdnice编辑器">缓存不是“挂在那就完事了”，它在执行循环中有比较明确的调用点。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">4.1 设置 prompt + 清理</span></h2>
<p data-tool="mdnice编辑器">启动一次执行时（<code style="color: #0e8aeb;">execution.py:681-685</code>）：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">is_changed_cache = IsChangedCache(prompt_id, dynamic_prompt, self.caches.outputs)
<span class="hljs-keyword" style="color: #c678dd;">for</span> cache <span class="hljs-keyword" style="color: #c678dd;">in</span> self.caches.all:
    <span class="hljs-keyword" style="color: #c678dd;">await</span> cache.set_prompt(dynamic_prompt, prompt.keys(), is_changed_cache)
    cache.clean_unused()
</code></pre>
<p data-tool="mdnice编辑器">这里做了三件事：</p>
<ol data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">给当前 prompt 生成一个 <code style="color: #0e8aeb;">IsChangedCache</code>，为 key 计算提供 is_changed 结果；</section>
</li>
<li>
<section style="color: #010101;">对 outputs / objects 各自执行一次 <code style="color: #0e8aeb;">set_prompt</code>（不同策略实现不同）；</section>
</li>
<li>
<section style="color: #010101;">紧接着执行 <code style="color: #0e8aeb;">clean_unused()</code>，做一次基于“当前 prompt 键集合”的清理。</section>
</li>
</ol>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">4.2 节点执行前后：cache 命中与写入</span></h2>
<p data-tool="mdnice编辑器">在节点执行路径中：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">执行前：优先尝试从 <code style="color: #0e8aeb;">outputs</code> 命中（<code style="color: #0e8aeb;">execution.py:686-701</code>）；</section>
</li>
<li>
<section style="color: #010101;">执行后：将 <code style="color: #0e8aeb;">(ui, outputs)</code> 作为 <code style="color: #0e8aeb;">CacheEntry</code> 写入 <code style="color: #0e8aeb;">outputs</code>（<code style="color: #0e8aeb;">execution.py:568-571</code>）。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">为了简化，这里只看抽象行为：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">命中 → 跳过计算，直接拿值；</section>
</li>
<li>
<section style="color: #010101;">未命中 → 正常跑一遍，将结果塞回缓存。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">4.3 每个节点之后的 RAM 轮询</span></h2>
<p data-tool="mdnice编辑器">如果是 RAM_PRESSURE 模式，执行完每个节点都会触发一次内存检查（<code style="color: #0e8aeb;">execution.py:720</code>）：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">self.caches.outputs.poll(ram_headroom=self.cache_args[<span class="hljs-string" style="color: #98c379;">"ram"</span>])
</code></pre>
<p data-tool="mdnice编辑器">只有 RAMPressureCache 实现了 <code style="color: #0e8aeb;">poll</code>，其他模式下这个调用等同空操作。</p>
<hr data-tool="mdnice编辑器" />
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">5. CLASSIC：默认层级缓存</span></h1>
<p data-tool="mdnice编辑器">先看默认策略：CLASSIC。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">5.1 初始化与结构</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">CacheSet.init_classic_cache</code>（<code style="color: #0e8aeb;">execution.py:97-126</code>）：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-class"><span class="hljs-keyword" style="color: #c678dd;">class</span> <span class="hljs-title" style="color: #e6c07b;">CacheType</span><span class="hljs-params">(Enum)</span>:</span>
    CLASSIC = <span class="hljs-number" style="color: #d19a66;">0</span>
    LRU = <span class="hljs-number" style="color: #d19a66;">1</span>
    NONE = <span class="hljs-number" style="color: #d19a66;">2</span>
    RAM_PRESSURE = <span class="hljs-number" style="color: #d19a66;">3</span>

<span class="hljs-class"><span class="hljs-keyword" style="color: #c678dd;">class</span> <span class="hljs-title" style="color: #e6c07b;">CacheSet</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">init_classic_cache</span><span class="hljs-params">(self)</span>:</span>
        self.outputs = HierarchicalCache(CacheKeySetInputSignature)
        self.objects = HierarchicalCache(CacheKeySetID)
</code></pre>
<p data-tool="mdnice编辑器">可以看到：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">outputs</code> 用层级缓存 + 输入签名做 key；</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">objects</code> 也用层级缓存 + (node_id, class_type) 做 key；</section>
</li>
<li>
<section style="color: #010101;">不涉及 LRU 或 RAM 驱逐。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">5.2 CLASSIC 的过期机制：完全由「当前 prompt」驱动</span></h2>
<p data-tool="mdnice编辑器">CLASSIC 模式不做容量和时间管理，它只有两种“失效”方式：</p>
<ol data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">提示切换 / 执行前清理</strong></section>
</li>
</ol>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">clean_unused()</code> 的核心逻辑（<code style="color: #0e8aeb;">comfy_execution/caching.py:172-195</code>）：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">clean_unused</span><span class="hljs-params">(self)</span>:</span>
    self._clean_cache()
    self._clean_subcaches()
</code></pre>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">_clean_cache()</code>：把不在“当前 prompt 键集合”的项删掉；</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">_clean_subcaches()</code>：把不再需要的子缓存分区删掉。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">在 <code style="color: #0e8aeb;">execution.py:681-685</code> 每次绑定新 prompt 时，都会执行这一步。结果是：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">换了一个新的 workflow / prompt，旧 workflow 的 outputs / objects 都会被视为“未使用”，被清理掉；</section>
</li>
<li>
<section style="color: #010101;">CLASSIC 不会跨不同 prompt 保留旧 workflow 的缓存。</section>
</li>
</ul>
<ol start="2" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">键不命中（指纹失效）</strong></section>
</li>
</ol>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">is_changed</code> 的计算在 <code style="color: #0e8aeb;">execution.py:48-89</code>，当节点输入更新时，指纹会变化；<code style="color: #0e8aeb;">CacheKeySetInputSignature</code> 在构造键时会把这个指纹带进去（<code style="color: #0e8aeb;">comfy_execution/caching.py:115-127</code>）。因此只要：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">参数 / 输入 / 上游节点的任一变化 → key 改变 → 旧值自然不命中。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">5.3 CLASSIC 明确不会做的事</span></h2>
<p data-tool="mdnice编辑器">在 CLASSIC 下：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">不做 LRU 容量控制：没有 max_size，也没有代际淘汰逻辑；</section>
</li>
<li>
<section style="color: #010101;">不做 RAM 压力驱逐：<code style="color: #0e8aeb;">poll()</code> 是空的，执行循环里即使调用了也什么都不干；</section>
</li>
<li>
<section style="color: #010101;">不做 TTL：不看时间，只看 prompt 键集合。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">对应的注释已经在参考内容中点得很清楚，这里就不重复堆代码了。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">5.4 在频繁切换 workflow / 模型时的表现</span></h2>
<p data-tool="mdnice编辑器">结合上面的机制，总结一下 CLASSIC 在多 workflow 场景下的行为：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">执行新工作流时：<br />
<code style="color: #0e8aeb;">set_prompt + clean_unused</code> 会直接把“不在新 prompt 键集合里”的缓存项（包括对象子缓存）全部清掉；</section>
</li>
<li>
<section style="color: #010101;">模型 / LoRA 变化：<br />
即便节点 ID 不变，输入签名和 is_changed 指纹不同，也会生成新键；旧条目先不命中，随后在下一次 prompt 绑定时被清空；</section>
</li>
<li>
<section style="color: #010101;">回切旧 workflow：<br />
因为在上一次切换时已经把旧 workflow 相关缓存清干净了，所以基本等于重新计算。</section>
</li>
</ul>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">适用场景</strong>：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">workflow 比较固定；</section>
</li>
<li>
<section style="color: #010101;">主要想在“一次执行当中”复用中间结果，不在意跨 prompt 的持久化。</section>
</li>
</ul>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">6. LRU：代际 LRU 控制 outputs 尺寸</span></h1>
<p data-tool="mdnice编辑器">第二种策略是 LRU，主要解决的问题是：<strong style="color: #0e88eb;">在允许跨 prompt 复用输出的前提下，限制缓存总量，避免无限膨胀</strong>。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">6.1 初始化：只作用于 outputs</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">CacheSet.init_lru_cache</code>（<code style="color: #0e8aeb;">execution.py:127-135</code>）：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">init_lru_cache</span><span class="hljs-params">(self, cache_size)</span>:</span>
    self.outputs = LRUCache(CacheKeySetInputSignature, max_size=cache_size)
    self.objects = HierarchicalCache(CacheKeySetID)
</code></pre>
<p data-tool="mdnice编辑器">注意几点：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">LRU <strong style="color: #0e88eb;">只作用于 outputs</strong>；</section>
</li>
<li>
<section style="color: #010101;">objects 仍然用 <code style="color: #0e8aeb;">HierarchicalCache</code>，不受 LRU 驱逐；</section>
</li>
<li>
<section style="color: #010101;">启动方式：<code style="color: #0e8aeb;">--cache-lru N</code>，且 <code style="color: #0e8aeb;">N &gt; 0</code>。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">6.2 LRU 的代际设计</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">LRUCache</code> 的骨架逻辑在 <code style="color: #0e8aeb;">comfy_execution/caching.py:299-337</code>：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-class"><span class="hljs-keyword" style="color: #c678dd;">class</span> <span class="hljs-title" style="color: #e6c07b;">LRUCache</span><span class="hljs-params">(BasicCache)</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">__init__</span><span class="hljs-params">(self, key_class, max_size=<span class="hljs-number" style="color: #d19a66;">100</span>)</span>:</span>
        self.max_size = max_size
        self.min_generation = <span class="hljs-number" style="color: #d19a66;">0</span>
        self.generation = <span class="hljs-number" style="color: #d19a66;">0</span>
        self.used_generation = {}
        self.children = {}

    <span class="hljs-keyword" style="color: #c678dd;">async</span> <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">set_prompt</span><span class="hljs-params">(...)</span>:</span>
        self.generation += <span class="hljs-number" style="color: #d19a66;">1</span>
        <span class="hljs-keyword" style="color: #c678dd;">for</span> node_id <span class="hljs-keyword" style="color: #c678dd;">in</span> node_ids:
            self._mark_used(node_id)

    <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">get</span><span class="hljs-params">(self, node_id)</span>:</span>
        self._mark_used(node_id)
        <span class="hljs-keyword" style="color: #c678dd;">return</span> self._get_immediate(node_id)

    <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">_mark_used</span><span class="hljs-params">(self, node_id)</span>:</span>
        cache_key = self.cache_key_set.get_data_key(node_id)
        <span class="hljs-keyword" style="color: #c678dd;">if</span> cache_key <span class="hljs-keyword" style="color: #c678dd;">is</span> <span class="hljs-keyword" style="color: #c678dd;">not</span> <span class="hljs-literal" style="color: #56b6c2;">None</span>:
            self.used_generation[cache_key] = self.generation
</code></pre>
<p data-tool="mdnice编辑器">含义：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">有一个全局代数 <code style="color: #0e8aeb;">generation</code>，每次绑定新 prompt <code style="color: #0e8aeb;">generation += 1</code>；</section>
</li>
<li>
<section style="color: #010101;">每次：</p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;">绑定 prompt 时，会把该 prompt 内所有节点标记为“在当前代被使用”；</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">get</code> / <code style="color: #0e8aeb;">set</code> 时更新条目的 <code style="color: #0e8aeb;">used_generation[key]</code> 为当前代。</section>
</li>
</ul>
</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">6.3 容量驱逐：按「最老代」逐步清理</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">clean_unused()</code> 的一部分逻辑在 <code style="color: #0e8aeb;">comfy_execution/caching.py:314-323</code>：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">clean_unused</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-keyword" style="color: #c678dd;">while</span> len(self.cache) &gt; self.max_size <span class="hljs-keyword" style="color: #c678dd;">and</span> self.min_generation &lt; self.
    generation:
        self.min_generation += <span class="hljs-number" style="color: #d19a66;">1</span>
        to_remove = [key <span class="hljs-keyword" style="color: #c678dd;">for</span> key <span class="hljs-keyword" style="color: #c678dd;">in</span> self.cache <span class="hljs-keyword" style="color: #c678dd;">if</span> self.used_generation[key] &lt; self.
        min_generation]
        <span class="hljs-keyword" style="color: #c678dd;">for</span> key <span class="hljs-keyword" style="color: #c678dd;">in</span> to_remove:
            <span class="hljs-keyword" style="color: #c678dd;">del</span> self.cache[key]
            <span class="hljs-keyword" style="color: #c678dd;">del</span> self.used_generation[key]
            <span class="hljs-keyword" style="color: #c678dd;">if</span> key <span class="hljs-keyword" style="color: #c678dd;">in</span> self.children:
                <span class="hljs-keyword" style="color: #c678dd;">del</span> self.children[key]
    self._clean_subcaches()
</code></pre>
<p data-tool="mdnice编辑器">简单归纳一下：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">只要 <code style="color: #0e8aeb;">len(cache) &gt; max_size</code>，就逐步提升 <code style="color: #0e8aeb;">min_generation</code>；</section>
</li>
<li>
<section style="color: #010101;">每提升一代，就删除“最近使用代 &lt; min_generation”的条目；</section>
</li>
<li>
<section style="color: #010101;">同步清除掉和这些 key 绑定的子缓存引用；</section>
</li>
<li>
<section style="color: #010101;">清理完再执行 <code style="color: #0e8aeb;">_clean_subcaches()</code> 做层级清扫。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">6.4 子图分区与代际配合</span></h2>
<p data-tool="mdnice编辑器">子缓存创建时会显式标记父节点和子节点“被使用”，避免刚刚生成的子图被误删。代码在 <code style="color: #0e8aeb;">comfy_execution/caching.py:338-349</code>：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-keyword" style="color: #c678dd;">async</span> <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">ensure_subcache_for</span><span class="hljs-params">(self, node_id, children_ids)</span>:</span>
    <span class="hljs-keyword" style="color: #c678dd;">await</span> super()._ensure_subcache(node_id, children_ids)
    <span class="hljs-keyword" style="color: #c678dd;">await</span> self.cache_key_set.add_keys(children_ids)
    self._mark_used(node_id)
    cache_key = self.cache_key_set.get_data_key(node_id)
    self.children[cache_key] = []
    <span class="hljs-keyword" style="color: #c678dd;">for</span> child_id <span class="hljs-keyword" style="color: #c678dd;">in</span> children_ids:
        self._mark_used(child_id)
        self.children[cache_key].append(self.cache_key_set.get_data_key(child_id))
    <span class="hljs-keyword" style="color: #c678dd;">return</span> self
</code></pre>
<p data-tool="mdnice编辑器">配合前面提到的层级结构，就形成了“按 workflow 子图分区 + LRU 按代际清理”的整体行为。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">6.5 触发时机和行为总结</span></h2>
<p data-tool="mdnice编辑器">在 LRU 模式下：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">每次绑定 prompt：<br />
<code style="color: #0e8aeb;">generation += 1</code>，标记当前 prompt 的节点使用代为当前代；</section>
</li>
<li>
<section style="color: #010101;">每次 <code style="color: #0e8aeb;">get/set</code>：<br />
更新条目的 <code style="color: #0e8aeb;">used_generation</code> 为当前代；</section>
</li>
<li>
<section style="color: #010101;">每次 <code style="color: #0e8aeb;">clean_unused()</code>：</p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;">当 <code style="color: #0e8aeb;">len(cache) &gt; max_size</code> 时，通过提升 <code style="color: #0e8aeb;">min_generation</code> 清除旧代条目；</section>
</li>
<li>
<section style="color: #010101;">额外清理无用子缓存。</section>
</li>
</ul>
</section>
</li>
</ul>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">特点</strong>：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">可以跨 prompt 保留一部分中间结果；</section>
</li>
<li>
<section style="color: #010101;">由 <code style="color: #0e8aeb;">max_size</code> 控制缓存上限；</section>
</li>
<li>
<section style="color: #010101;">没有 RAM 压力感知：<code style="color: #0e8aeb;">poll()</code> 依然不做事。</section>
</li>
</ul>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">适用场景</strong>：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">希望在多 workflow 之间部分复用缓存；</section>
</li>
<li>
<section style="color: #010101;">但机器内存有限，需要给 outputs 一个明确的容量上限；</section>
</li>
<li>
<section style="color: #010101;">对 RAM 细粒度控制没有强需求，或使用的是物理机 / 内存足够的环境。</section>
</li>
</ul>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">7. RAM_PRESSURE：按可用内存压力驱逐</span></h1>
<p data-tool="mdnice编辑器">第三种策略是 RAM_PRESSURE，对应类是 <code style="color: #0e8aeb;">RAMPressureCache</code>。它继承自 <code style="color: #0e8aeb;">LRUCache</code>，但不按 max_size 做驱逐，而是：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">通过 <code style="color: #0e8aeb;">poll(ram_headroom)</code>，在可用内存不足时按“OOM 评分”驱逐条目。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">7.1 初始化：objects 仍然是层级缓存</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">CacheSet.init_ram_cache</code>（<code style="color: #0e8aeb;">execution.py:131-133</code>）：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">init_ram_cache</span><span class="hljs-params">(self, min_headroom)</span>:</span>
    self.outputs = RAMPressureCache(CacheKeySetInputSignature)
    self.objects = HierarchicalCache(CacheKeySetID)
</code></pre>
<p data-tool="mdnice编辑器">注意两个点：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">RAM 模式下，<strong style="color: #0e88eb;">只有 outputs 会按 RAM 压力驱逐</strong>；</section>
</li>
<li>
<section style="color: #010101;">objects 不参与 RAM 驱逐，逻辑完全和 CLASSIC/LRU 下相同。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">7.2 poll：可用 RAM 检测 + OOM 评分驱逐</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">poll</code> 的主逻辑在 <code style="color: #0e8aeb;">comfy_execution/caching.py:384-454</code>：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">poll</span><span class="hljs-params">(self, ram_headroom)</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">_ram_gb</span><span class="hljs-params">()</span>:</span>
        <span class="hljs-comment" style="font-style: italic; color: #5c6370;"># 优先 cgroup v2/v1，失败回退 psutil</span>
        ...
    <span class="hljs-keyword" style="color: #c678dd;">if</span> _ram_gb() &gt; ram_headroom: <span class="hljs-keyword" style="color: #c678dd;">return</span>
    gc.collect()
    <span class="hljs-keyword" style="color: #c678dd;">if</span> _ram_gb() &gt; ram_headroom: <span class="hljs-keyword" style="color: #c678dd;">return</span>
    clean_list = []
    <span class="hljs-keyword" style="color: #c678dd;">for</span> key, (outputs, _), <span class="hljs-keyword" style="color: #c678dd;">in</span> self.cache.items():
        oom_score = RAM_CACHE_OLD_WORKFLOW_OOM_MULTIPLIER ** (self.generation -
        self.used_generation[key])
        ram_usage = RAM_CACHE_DEFAULT_RAM_USAGE
        <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">scan_list_for_ram_usage</span><span class="hljs-params">(outputs)</span>:</span>
            <span class="hljs-keyword" style="color: #c678dd;">nonlocal</span> ram_usage
            ...
        scan_list_for_ram_usage(outputs)
        oom_score *= ram_usage
        bisect.insort(clean_list, (oom_score, self.timestamps[key], key))
    <span class="hljs-keyword" style="color: #c678dd;">while</span> _ram_gb() &lt; ram_headroom * RAM_CACHE_HYSTERESIS <span class="hljs-keyword" style="color: #c678dd;">and</span> clean_list:
        _, _, key = clean_list.pop()
        <span class="hljs-keyword" style="color: #c678dd;">del</span> self.cache[key]
        gc.collect()
</code></pre>
<p data-tool="mdnice编辑器">流程拆一下：</p>
<ol data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">
<p style="color: #000000;"><strong style="color: #0e88eb;">获取可用 RAM</strong></p>
<p style="color: #000000;"><code style="color: #0e8aeb;">_ram_gb()</code> 的实现优先读取 cgroup 的限制：</p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;">cgroup v2：<code style="color: #0e8aeb;">memory.max</code> / <code style="color: #0e8aeb;">memory.current</code>；</section>
</li>
<li>
<section style="color: #010101;">cgroup v1：<code style="color: #0e8aeb;">memory.limit_in_bytes</code> / <code style="color: #0e8aeb;">memory.usage_in_bytes</code>；</section>
</li>
<li>
<section style="color: #010101;">都失败才回退 <code style="color: #0e8aeb;">psutil.virtual_memory().available</code>。</section>
</li>
</ul>
<p style="color: #000000;">这解决了容器环境下“宿主机内存大，容器实际被限制”的常见问题。</p>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;"><strong style="color: #0e88eb;">阈值和 GC</strong></p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;">如果可用 RAM &gt; <code style="color: #0e8aeb;">ram_headroom</code>，直接返回；</section>
</li>
<li>
<section style="color: #010101;">否则先跑一次 <code style="color: #0e8aeb;">gc.collect()</code>；</section>
</li>
<li>
<section style="color: #010101;">再测一次 RAM，如果还是不足，进入驱逐流程。</section>
</li>
</ul>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;"><strong style="color: #0e88eb;">为每个条目计算 OOM 评分</strong></p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;">初始 <code style="color: #0e8aeb;">oom_score = RAM_CACHE_OLD_WORKFLOW_OOM_MULTIPLIER ** (generation - used_generation[key])</code>：</p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;">大概意思是：越久没被用，分数指数级放大（默认倍数为 1.3，见 <code style="color: #0e8aeb;">comfy_execution/caching.py:365</code>）；</section>
</li>
</ul>
</section>
</li>
<li>
<section style="color: #010101;">初始 <code style="color: #0e8aeb;">ram_usage = RAM_CACHE_DEFAULT_RAM_USAGE</code>（0.1，见 <code style="color: #0e8aeb;">360</code>）；</section>
</li>
<li>
<section style="color: #010101;">递归遍历 <code style="color: #0e8aeb;">outputs</code> 列表：</p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;">CPU tensor：<code style="color: #0e8aeb;">numel * element_size * 0.5</code>（认为 CPU 上的 tensor 价值更高，折半）；</section>
</li>
<li>
<section style="color: #010101;">自定义对象：如果实现了 <code style="color: #0e8aeb;">get_ram_usage()</code> 就加上它；</section>
</li>
</ul>
</section>
</li>
<li>
<section style="color: #010101;">最后 <code style="color: #0e8aeb;">oom_score *= ram_usage</code>，得到综合评分。</section>
</li>
</ul>
<p style="color: #000000;">所有条目按 <code style="color: #0e8aeb;">(oom_score, timestamp, key)</code> 排序，放入 <code style="color: #0e8aeb;">clean_list</code>。</p>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;"><strong style="color: #0e88eb;">按迟滞阈值逐个删除</strong></p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;">只要 <code style="color: #0e8aeb;">_ram_gb() &lt; ram_headroom * RAM_CACHE_HYSTERESIS</code>，就从 <code style="color: #0e8aeb;">clean_list</code> 末尾 pop 一个 key 并删除；</section>
</li>
<li>
<section style="color: #010101;">每删一个都跑一次 <code style="color: #0e8aeb;">gc.collect()</code>；</section>
</li>
<li>
<section style="color: #010101;">迟滞倍数 <code style="color: #0e8aeb;">RAM_CACHE_HYSTERESIS</code> 默认 1.1，避免“刚删完又马上触发清理”的抖动。</section>
</li>
</ul>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;"><strong style="color: #0e88eb;">访问时间戳</strong></p>
<p style="color: #000000;">访问时会更新 timestamps（<code style="color: #0e8aeb;">comfy_execution/caching.py:376-382</code>）：</p>
</section>
</li>
</ol>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">   <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">set</span><span class="hljs-params">(self, node_id, value)</span>:</span>
       self.timestamps[self.cache_key_set.get_data_key(node_id)] = time.time()
       super().set(node_id, value)

   <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">def</span> <span class="hljs-title" style="color: #61aeee;">get</span><span class="hljs-params">(self, node_id)</span>:</span>
       self.timestamps[self.cache_key_set.get_data_key(node_id)] = time.time()
       <span class="hljs-keyword" style="color: #c678dd;">return</span> super().get(node_id)
</code></pre>
<p data-tool="mdnice编辑器">在 oom_score 一样时，timestamp 起到“最近访问优先保留”的作用。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">7.3 提示绑定下的行为：不清理 outputs</span></h2>
<p data-tool="mdnice编辑器">RAM 模式下，<code style="color: #0e8aeb;">clean_unused()</code> 的行为与 CLASSIC 不同（见参考说明）：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">RAM 模式：<br />
<code style="color: #0e8aeb;">clean_unused()</code> 只做子缓存分区清理，不会删掉“当前 prompt 未使用”的 outputs 条目；</section>
</li>
<li>
<section style="color: #010101;">CLASSIC 模式：<br />
<code style="color: #0e8aeb;">clean_unused()</code> 会同时删掉当前 prompt 未用到的 outputs 条目。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">结果是：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">RAM 模式可以跨多个 workflow 长时间保留中间结果；</section>
</li>
<li>
<section style="color: #010101;">只有在 RAM 不够时才做清退。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">7.4 容器环境下需要注意的点</span></h2>
<p data-tool="mdnice编辑器">的 AutoDL 中，用 <code style="color: #0e8aeb;">psutil.virtual_memory().available</code>，在容器里看到的是宿主机内存，而不是容器的限额，导致永远“不触发回收”，最后 OOM。</p>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">适用场景</strong>：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">多 workflow / 多模型 / 多 LoRA 同时存在，且希望尽可能长时间复用输出结果；</section>
</li>
<li>
<section style="color: #010101;">机器内存有限，但更关心“不 OOM”，而不是一个固定的 <code style="color: #0e8aeb;">max_size</code>；</section>
</li>
<li>
<section style="color: #010101;">特别适合容器环境（K8s / AutoDL 等），配合 <code style="color: #0e8aeb;">--cache-ram &lt;GB&gt;</code>。</section>
</li>
</ul>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">8. 一些落地建议</span></h1>
<p data-tool="mdnice编辑器">最后，用一段比较直接的建议收尾。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">8.1 单模型 / workflow 稳定：用 CLASSIC 即可</span></h2>
<p data-tool="mdnice编辑器">特点：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">工作流基本不换；</section>
</li>
<li>
<section style="color: #010101;">主要希望避免一次执行中的重复计算（比如多次 preview）。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">用默认 CLASSIC：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">结构简单；</section>
</li>
<li>
<section style="color: #010101;">不参与复杂的跨 prompt 保留；</section>
</li>
<li>
<section style="color: #010101;">不需要担心 LRU 尺寸和 RAM 阈值调参。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">8.2 多 workflow + 控制缓存尺寸：用 LRU</span></h2>
<p data-tool="mdnice编辑器">场景：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">有多套 workflow，在它们之间来回切；</section>
</li>
<li>
<section style="color: #010101;">机器内存不是特别大，希望 outputs 不要无限膨胀；</section>
</li>
<li>
<section style="color: #010101;">又希望某些常用子图能被复用。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">做法：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">启动时加 <code style="color: #0e8aeb;">--cache-lru N</code>（N 先给一个相对保守的值，比如几百到几千条，看内存曲线再调）；</section>
</li>
<li>
<section style="color: #010101;">让 <code style="color: #0e8aeb;">LRUCache</code> 用代际 + max_size 帮你自动做“近期常用保留、早期冷门清理”。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">8.3 多模型 / 多 LoRA / 容器环境：优先 RAM_PRESSURE</span></h2>
<p data-tool="mdnice编辑器">场景：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">容器 / 云平台（K8s、AutoDL 等）；</section>
</li>
<li>
<section style="color: #010101;">有较多模型、LoRA 和 workflow 混用；</section>
</li>
<li>
<section style="color: #010101;">内存被容器限制，容易因爆 RAM 掉进 OOM。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">做法：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">启动时用 <code style="color: #0e8aeb;">--cache-ram &lt;GB&gt;</code> 配一个“希望保留的 RAM 余量”；</p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;">比如容器给了 32GB，就设在 16–24GB 看情况；</section>
</li>
</ul>
</section>
</li>
<li>
<section style="color: #010101;">让 <code style="color: #0e8aeb;">RAMPressureCache</code>：</p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;">最大程度保留各个 workflow 的中间结果（包括应用 LoRA 后的模型对象等）；</section>
</li>
<li>
<section style="color: #010101;">在内存不足时，根据 OOM 评分优先清旧代、大内存条目。</section>
</li>
</ul>
</section>
</li>
</ul>
<p data-tool="mdnice编辑器">注意一点：即使有容器优化，如果平台本身的 cgroup 挂得不标准，<code style="color: #0e8aeb;">_ram_gb()</code> 的结果还是有可能偏离实际，这一点要结合平台文档确认。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">8.4 完全不想折腾：直接关缓存</span></h2>
<p data-tool="mdnice编辑器">如果你：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">环境受限；</section>
</li>
<li>
<section style="color: #010101;">或者调试阶段不想被缓存影响行为理解；</section>
</li>
</ul>
<p data-tool="mdnice编辑器">可以直接用：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">--cache-none</code><br />
对应 <code style="color: #0e8aeb;">CacheType.NONE</code>，<code style="color: #0e8aeb;">CacheSet.init_null_cache()</code> 走 <code style="color: #0e8aeb;">NullCache</code>，所有 get/set 都是 no-op。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">代价就是：每次执行都完全重新算一遍。</p>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">9. 小结一下</span></h1>
<p data-tool="mdnice编辑器">把上面的内容压缩成几句话：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">
<p style="color: #000000;">ComfyUI 有两路缓存：</p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">outputs</code> 存中间结果，真正用来省算力；</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">objects</code> 存节点实例，只减少 Python 对象构造开销。</section>
</li>
</ul>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;">键体系：</p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;">输出：<strong style="color: #0e88eb;">输入签名 + is_changed 指纹（+ 条件下的 node_id）</strong>；</section>
</li>
<li>
<section style="color: #010101;">对象：**(node_id, class_type)**。</section>
</li>
</ul>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;">四种策略：</p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;">CLASSIC：默认层级缓存，按当前 prompt 键集合清理，不做 LRU / RAM 驱逐；</section>
</li>
<li>
<section style="color: #010101;">LRU：只对 outputs 做代际 LRU，配合 <code style="color: #0e8aeb;">max_size</code> 控制容量；</section>
</li>
<li>
<section style="color: #010101;">RAM_PRESSURE：在 LRU 基础上加 RAM 压力驱逐，在内存不足时按 OOM 评分清理；</section>
</li>
<li>
<section style="color: #010101;">NONE：彻底关掉缓存。</section>
</li>
</ul>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;">在多模型 / 多 workflow / 多 LoRA 场景下：</p>
<ul style="color: #000000;">
<li>
<section style="color: #010101;">对象缓存 <code style="color: #0e8aeb;">objects</code> 始终是层级缓存，不参与 LRU / RAM 驱逐，在每次绑定 prompt 时按键集合清理；</section>
</li>
<li>
<section style="color: #010101;">模型权重 / LoRA 的真实驻留由模型管理层控制；</section>
</li>
<li>
<section style="color: #010101;">真正要关心的是：如何选择 outputs 的策略，让中间结果既能有效复用，又不会把内存打爆。</section>
</li>
</ul>
</section>
</li>
</ul>
<p data-tool="mdnice编辑器">如果我们在一个复杂的工作流环境里跑 ComfyUI，建议先搞清楚自己处于哪种场景，再结合上面的策略选项，把缓存调成我们能控制的状态，而不是让它在后台「自动长草」。</p>
<p data-tool="mdnice编辑器">以上。</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2025/11/comfyui-cache/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>深入源码解析 ComfyUI 的模块化节点设计架构</title>
		<link>https://www.phppan.com/2024/11/comfyui-node-system-source-analysis/</link>
		<comments>https://www.phppan.com/2024/11/comfyui-node-system-source-analysis/#comments</comments>
		<pubDate>Sat, 16 Nov 2024 01:25:39 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[AIGC]]></category>
		<category><![CDATA[AI架构]]></category>

		<guid isPermaLink="false">http://www.phppan.com/?p=2294</guid>
		<description><![CDATA[ComfyUI 是一个基于 Stable Diffusion 的开源 AI 绘图工具，采用了模块化的节点式工作 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 是一个基于 Stable Diffusion 的开源 AI 绘图工具，采用了<strong style="color: #0e88eb;">模块化的节点式工作流设计</strong>。它通过将 Stable Diffusion 的各个组件和处理步骤抽象为独立的节点，使得用户可以通过直观的拖拽、连接操作来构建复杂的图像生成流程。</p>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 解决了传统 AI 绘图工具易用性差、扩展性低的问题。其模块化设计和直观的 Web 界面大大降低了用户的使用门槛，无需深入了解底层技术细节，即可快速构建和调整工作流。同时，ComfyUI 还提供了强大的自定义节点机制，允许开发者轻松扩展新的功能和模型，使其能够适应不断发展的AI绘图领域。</p>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 最初由开发者 Comfyanonymous 在 2022 年末发起，旨在提供一个简单、直观的 Stable Diffusion Web UI。<strong style="color: #0e88eb;">早期版本实现了基本的节点类型和 Web 界面</strong>，展示了其模块化设计的优势，吸引了一批 AI 绘图爱好者的关注。</p>
<p style="color: #000000;" data-tool="mdnice编辑器">在 2023 年春夏，ComfyUI 进入了快速发展阶段。项目不断增加新的节点类型，如 ControlNet、Inpaint、Upscale等，支持更多的图像控制和后处理功能。同时，ComfyUI 引入了自定义节点机制，大大扩展了其功能和适用范围。项目也集成了更多 Stable Diffusion 衍生模型，为用户提供了更多选择。</p>
<p style="color: #000000;" data-tool="mdnice编辑器">随着用户社区的不断壮大，ComfyUI 的生态也日益丰富。社区成员积极贡献工作流、节点脚本、训练模型等资源，推动项目的发展。ComfyUI 举办了一系列社区活动，促进了用户间的交流和创作。项目代码库也迎来了更多贡献者，社区力量成为 ComfyUI 发展的重要推动力。</p>
<p style="color: #000000;" data-tool="mdnice编辑器">2023 年冬开始，ComfyUI 开始着眼于生态融合和应用拓展。项目与其他 AI 绘图工具建立了联系，支持工作流的导入导出和 API 集成。ComfyUI 也开始探索更多应用场景，如虚拟主播、游戏 mod 等，拓宽了 AI绘图的应用范围。越来越多的开发者和公司开始关注和使用 ComfyUI，其发展前景备受看好。</p>
<p style="color: #000000;" data-tool="mdnice编辑器">前两周，Comfy 推出跨平台的 ComfyUI 安装包，现在我们可以一键安装 ComfyUI 了，这次更新不仅带来了全新的桌面版应用，还对用户界面进行了全面升级，并新增了自定义按键绑定、自动资源导入等一系列实用功能，让工作流程更加流畅。</p>
<p style="color: #000000;" data-tool="mdnice编辑器">今天我们深入到 ComyUI 的源码去看一下其实现原理和过程。</p>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 执行的大概流程如下：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;">用户界面 -&gt; 工作流定义 -&gt; PromptQueue
   ↓
PromptExecutor 开始执行
   ↓
验证输入 (validate_prompt)
   ↓
构建执行图
   ↓
按顺序执行节点
   ↓
缓存结果
   ↓
返回结果到界面

</code></pre>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">1. ComfyUI 的初始化与执行流程详解</span></h1>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 的一个明显的优点是有着灵活的图形用户界面，可以用于处理复杂的图像生成和处理工作流。</p>
<p style="color: #000000;" data-tool="mdnice编辑器">它具有精良的架构设计，通过模块化设计、缓存优化、资源管理以及错误处理机制，确保了系统的高效性和可靠性。</p>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">1.1 系统初始化流程</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 的启动过程分为几个主要阶段：预启动脚本的执行、节点系统的初始化、服务器的启动与 WebSocket 的连接。</p>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">1. 启动前准备</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">在系统启动前，ComfyUI 会首先执行预启动脚本，确保自定义节点的环境准备就绪。这一过程允许在加载节点之前执行一些必要的自定义操作。</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">def</span> <span style="color: #61aeee;">execute_prestartup_script</span>():
    <span style="font-style: italic; color: #5c6370;"># 执行自定义节点的预启动脚本</span>
    <span style="color: #c678dd;">for</span> custom_node_path <span style="color: #c678dd;">in</span> node_paths:
        <span style="color: #c678dd;">if</span> os.path.exists(script_path):
            execute_script(script_path)
</code></pre>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">2. 节点系统初始化</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">节点系统是 ComfyUI 的核心组件。此阶段会加载内置节点以及用户自定义的节点，并注册到系统中供后续使用。</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">def</span> <span style="color: #61aeee;">init_extra_nodes</span>():
    <span style="font-style: italic; color: #5c6370;"># 加载内置节点</span>
    import_failed = init_builtin_extra_nodes()
    <span style="font-style: italic; color: #5c6370;"># 加载自定义节点</span>
    init_external_custom_nodes()
</code></pre>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">内置节点</strong>: 位于 <code style="color: #0e8aeb;">comfy_extras</code> 目录下，定义了基本的图像处理功能。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">自定义节点</strong>: 用户可以通过 <code style="color: #0e8aeb;">custom_nodes</code> 目录添加自定义节点，扩展系统的功能。</section>
</li>
</ul>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">3. 服务器初始化</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">服务器初始化是启动 ComfyUI 的 Web 服务器的过程。它包括 WebSocket 的初始化，允许前端和后端实时通信。此外，执行队列也会在此阶段创建，用于管理节点的执行顺序和任务调度。</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">class</span> <span style="color: #e6c07b;">PromptServer</span>:
    <span style="color: #c678dd;">def</span> <span style="color: #61aeee;">__init__</span>(self, loop):
        <span style="font-style: italic; color: #5c6370;"># 初始化 Web 服务器</span>
        self.app = web.Application()
        <span style="font-style: italic; color: #5c6370;"># 初始化 WebSocket</span>
        self.sockets = dict()
        <span style="font-style: italic; color: #5c6370;"># 初始化执行队列</span>
        self.prompt_queue = <span style="color: #56b6c2;">None</span>
</code></pre>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">1.2 工作流执行流程</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">工作流执行是 ComfyUI 的核心功能，它包括从提交工作流到执行节点的整个过程。以下是工作流执行的几个关键步骤。</p>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">1. 工作流验证</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">首先，系统会对提交的工作流进行验证，确保节点的类型存在、节点连接有效，并且每个节点的输入符合要求。</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">def</span> <span style="color: #61aeee;">validate_prompt</span>(prompt):
    <span style="font-style: italic; color: #5c6370;"># 1. 验证节点类型是否存在</span>
    <span style="font-style: italic; color: #5c6370;"># 2. 验证是否有输出节点</span>
    <span style="font-style: italic; color: #5c6370;"># 3. 验证节点输入</span>
    <span style="color: #c678dd;">return</span> (valid, error, good_outputs, node_errors)
</code></pre>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">2. 执行准备</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">在验证通过后，系统会初始化执行环境。这包括创建动态的提示（<code style="color: #0e8aeb;">DynamicPrompt</code>），以及初始化缓存系统，以避免重复计算并提高执行效率。</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">def</span> <span style="color: #61aeee;">execute</span>(self, prompt, prompt_id, extra_data={}, execute_outputs=[]):
    <span style="font-style: italic; color: #5c6370;"># 1. 初始化执行环境</span>
    <span style="color: #c678dd;">with</span> torch.inference_mode():
        <span style="font-style: italic; color: #5c6370;"># 2. 创建动态提示</span>
        dynamic_prompt = DynamicPrompt(prompt)
        <span style="font-style: italic; color: #5c6370;"># 3. 初始化缓存</span>
        is_changed_cache = IsChangedCache(dynamic_prompt, self.caches.outputs)
</code></pre>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">3. 节点执行</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">每个节点的执行流程包括获取节点的输入数据、检查是否有缓存的数据可以复用、执行节点逻辑、并缓存执行结果。节点执行是系统的核心环节，其过程如下：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">def</span> <span style="color: #61aeee;">execute</span>(server, dynprompt, caches, current_item, extra_data, executed, prompt_id, execution_list, pending_subgraph_results):
    <span style="font-style: italic; color: #5c6370;"># 1. 获取节点信息</span>
    unique_id = current_item
    inputs = dynprompt.get_node(unique_id)[<span style="color: #98c379;">'inputs'</span>]
    class_type = dynprompt.get_node(unique_id)[<span style="color: #98c379;">'class_type'</span>]
    
    <span style="font-style: italic; color: #5c6370;"># 2. 检查缓存</span>
    <span style="color: #c678dd;">if</span> caches.outputs.get(unique_id) <span style="color: #c678dd;">is</span> <span style="color: #c678dd;">not</span> <span style="color: #56b6c2;">None</span>:
        <span style="color: #c678dd;">return</span> (ExecutionResult.SUCCESS, <span style="color: #56b6c2;">None</span>, <span style="color: #56b6c2;">None</span>)
    
    <span style="font-style: italic; color: #5c6370;"># 3. 获取输入数据</span>
    input_data_all, missing_keys = get_input_data(inputs, class_def, unique_id, caches.outputs, dynprompt, extra_data)
    
    <span style="font-style: italic; color: #5c6370;"># 4. 执行节点</span>
    output_data, output_ui, has_subgraph = get_output_data(obj, input_data_all)
    
    <span style="font-style: italic; color: #5c6370;"># 5. 缓存结果</span>
    caches.ui.set(unique_id, {...})
</code></pre>
<ol class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">获取节点信息</strong>: 获取当前节点的输入和类型信息。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">检查缓存</strong>: 如果节点的输出已经缓存，则直接返回缓存结果，避免重复执行。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">获取输入数据</strong>: 从上一个节点或缓存中获取需要的输入数据。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">执行节点</strong>: 调用节点的执行函数，处理输入并生成输出数据。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">缓存结果</strong>: 将执行结果缓存，以便后续节点使用。</section>
</li>
</ol>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">1.3 执行队列管理</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 通过执行队列管理工作流中的节点执行顺序。每个节点的执行任务会被放入队列中，系统按顺序处理这些任务。</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">def</span> <span style="color: #61aeee;">prompt_worker</span>(q, server):
    e = execution.PromptExecutor(server, lru_size=args.cache_lru)
    
    <span style="color: #c678dd;">while</span> <span style="color: #56b6c2;">True</span>:
        <span style="font-style: italic; color: #5c6370;"># 1. 获取队列任务</span>
        queue_item = q.get(timeout=timeout)
        
        <span style="font-style: italic; color: #5c6370;"># 2. 执行提示</span>
        e.execute(item[<span style="color: #d19a66;">2</span>], prompt_id, item[<span style="color: #d19a66;">3</span>], item[<span style="color: #d19a66;">4</span>])
        
        <span style="font-style: italic; color: #5c6370;"># 3. 资源管理</span>
        <span style="color: #c678dd;">if</span> need_gc:
            comfy.model_management.cleanup_models()
            gc.collect()
</code></pre>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">获取队列任务</strong>: 从队列中取出下一个需要执行的节点任务。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">执行节点</strong>: 调用执行器执行当前节点。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">资源管理</strong>: 在必要时触发模型清理和垃圾回收，确保系统资源不被过度占用。</section>
</li>
</ul>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">1.4 缓存系统</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 的缓存系统采用层次化设计，可以缓存节点的输出、对象和 UI 相关的数据，极大地提高了执行效率。</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">class</span> <span style="color: #e6c07b;">HierarchicalCache</span>:
    <span style="color: #c678dd;">def</span> <span style="color: #61aeee;">__init__</span>(self):
        self.outputs = {}  <span style="font-style: italic; color: #5c6370;"># 节点输出缓存</span>
        self.ui = {}  <span style="font-style: italic; color: #5c6370;"># UI 相关缓存</span>
        self.objects = {}  <span style="font-style: italic; color: #5c6370;"># 节点对象缓存</span>
</code></pre>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">输出缓存（outputs）</strong>: 缓存节点的执行结果，避免重复计算。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">对象缓存（objects）</strong>: 缓存大数据对象，如模型和图像，以减少加载时间。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">UI 缓存（ui）</strong>: 缓存与前端界面相关的信息，如预览图像和执行状态。</section>
</li>
</ul>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">1.5 资源管理与错误处理</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">为了确保系统的稳定性，ComfyUI 提供了完善的资源管理和错误处理机制。在执行工作流的过程中，系统会自动清理未使用的模型和缓存，并在必要时触发内存回收。</p>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">资源管理</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">资源管理包括内存清理、模型卸载以及缓存清理。系统会根据内存使用情况自动卸载不必要的模型，并定期触发垃圾回收。</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="font-style: italic; color: #5c6370;"># 1. 内存清理</span>
comfy.model_management.cleanup_models()
gc.collect()

<span style="font-style: italic; color: #5c6370;"># 2. 模型卸载</span>
comfy.model_management.unload_all_models()

<span style="font-style: italic; color: #5c6370;"># 3. 缓存清理</span>
cache.clean_unused()
</code></pre>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">错误处理</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 实现了详细的错误处理机制，能够捕获并处理执行过程中发生的各种异常。对于节点执行中的错误，系统会记录错误信息并通知用户。</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">def</span> <span style="color: #61aeee;">handle_execution_error</span>(self, prompt_id, prompt, current_outputs, executed, error, ex):
    <span style="font-style: italic; color: #5c6370;"># 1. 处理中断异常</span>
    <span style="color: #c678dd;">if</span> isinstance(ex, comfy.model_management.InterruptProcessingException):
        self.add_message(<span style="color: #98c379;">"execution_interrupted"</span>, mes)
    <span style="font-style: italic; color: #5c6370;"># 2. 处理其他错误</span>
    <span style="color: #c678dd;">else</span>:
        self.add_message(<span style="color: #98c379;">"execution_error"</span>, mes)
</code></pre>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">处理中断异常</strong>: 当执行被中断时，系统会捕获异常并记录中断信息。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">处理其他错误</strong>: 处理其他执行错误，并通过 UI 向用户报告错误详情。</section>
</li>
</ul>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">2. 节点系统架构</span></h1>
<p style="color: #000000;" data-tool="mdnice编辑器">节点系统是 ComfyUI 的核心系统，其节点系统架构设计精巧，支持动态节点的加载、执行和扩展。今天我们详细介绍 ComfyUI 的节点系统架构，涵盖节点定义、执行流程、缓存机制、扩展性和系统特性等方面。</p>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">2.1 节点系统的基础架构</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 的节点系统基于 Python 模块化设计，所有节点及其行为都通过类的形式进行定义。这些节点在启动时会进行注册，允许系统灵活地加载和使用内置节点与自定义节点。</p>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">核心节点定义与注册</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 的节点系统在 <code style="color: #0e8aeb;">nodes.py</code> 中定义，并通过以下映射存储所有节点类及其显示名称：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;">NODE_CLASS_MAPPINGS = {}  <span style="font-style: italic; color: #5c6370;"># 存储所有节点类的映射</span>
NODE_DISPLAY_NAME_MAPPINGS = {}  <span style="font-style: italic; color: #5c6370;"># 节点显示名称映射</span>
</code></pre>
<p style="color: #000000;" data-tool="mdnice编辑器">节点通过类定义，并包含以下几个关键属性：</p>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">INPUT_TYPES</strong>: 输入参数的类型定义。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">RETURN_TYPES</strong>: 返回数据的类型定义。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">FUNCTION</strong>: 节点的具体执行函数。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">CATEGORY</strong>: 节点的类别，用于在 UI 中分类显示。</section>
</li>
</ul>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">节点类型</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 支持两种类型的节点：</p>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">内置节点</strong>: 在系统启动时加载，存储在 <code style="color: #0e8aeb;">comfy_extras</code> 目录下。内置节点提供了常见的图像操作、模型加载和处理功能。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">自定义节点</strong>: 用户可以在 <code style="color: #0e8aeb;">custom_nodes</code> 目录中添加自定义节点，系统在启动时自动加载这些节点。</section>
</li>
</ul>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">节点加载机制</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 提供了灵活的节点加载机制，允许动态加载内置节点和自定义节点：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">def</span> <span style="color: #61aeee;">init_builtin_extra_nodes</span>():
    extras_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), <span style="color: #98c379;">"comfy_extras"</span>)
    extras_files = [<span style="color: #98c379;">"nodes_latent.py"</span>, <span style="color: #98c379;">"nodes_hypernetwork.py"</span>]
</code></pre>
<p style="color: #000000;" data-tool="mdnice编辑器">对于自定义节点，ComfyUI 使用动态模块导入的方式加载：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">def</span> <span style="color: #61aeee;">load_custom_node</span>(module_path: str, ignore=set(), module_parent=<span style="color: #98c379;">"custom_nodes"</span>) -&gt; bool:
    module_spec = importlib.util.spec_from_file_location(module_name, module_path)
    module = importlib.util.module_from_spec(module_spec)
    <span style="color: #c678dd;">if</span> hasattr(module, <span style="color: #98c379;">"NODE_CLASS_MAPPINGS"</span>):
        <span style="color: #c678dd;">for</span> name, node_cls <span style="color: #c678dd;">in</span> module.NODE_CLASS_MAPPINGS.items():
            NODE_CLASS_MAPPINGS[name] = node_cls
</code></pre>
<p style="color: #000000;" data-tool="mdnice编辑器">这种设计使得 ComfyUI 可以方便地扩展和加载新节点，用户可以根据需求自定义节点功能并动态加载。</p>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">2.2 节点执行系统</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">节点的执行逻辑由 <code style="color: #0e8aeb;">execution.py</code> 中的 <code style="color: #0e8aeb;">PromptExecutor</code> 类负责。该类管理了节点的执行流程，包括输入验证、节点函数执行、结果缓存和输出返回等。</p>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">执行流程</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">节点的执行流程如下：</p>
<ol class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">输入验证</strong>: 系统首先验证节点的输入是否符合预定义的输入类型。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">获取输入数据</strong>: 从上一个节点或缓存中获取节点的输入数据。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">执行节点函数</strong>: 根据定义的 <code style="color: #0e8aeb;">FUNCTION</code> 执行节点逻辑。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">缓存结果</strong>: 执行结果会缓存，避免重复计算。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">返回输出</strong>: 将执行结果返回给下游节点或 UI。</section>
</li>
</ol>
<p style="color: #000000;" data-tool="mdnice编辑器">执行器的核心代码如下：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">class</span> <span style="color: #e6c07b;">PromptExecutor</span>:
    <span style="color: #c678dd;">def</span> <span style="color: #61aeee;">execute</span>(self, prompt, prompt_id, extra_data=None, execute_outputs=[]):
        <span style="font-style: italic; color: #5c6370;"># 节点执行的主要逻辑</span>
</code></pre>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 通过此执行器确保节点按顺序执行，并管理节点间的数据流动。</p>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">2.3 缓存机制</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">为了提高性能，减少重复计算，ComfyUI 实现了多层缓存系统，缓存节点的输出、对象和 UI 相关数据。缓存的实现类为 <code style="color: #0e8aeb;">HierarchicalCache</code>，并且系统支持 <strong style="color: #0e88eb;">LRU（最近最少使用）</strong> 缓存策略。后续章节会详细讲一下缓存，这里先略过。</p>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">2.4 数据流与依赖管理</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 的节点系统依赖于图形化的数据流管理。节点之间通过输入和输出相互连接，系统会自动分析节点间的依赖关系，确保数据流在节点间正确传递。</p>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">节点图解析与执行顺序</strong></p>
<ol class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">节点图解析</strong>: ComfyUI 解析 JSON 格式的节点图，识别节点之间的连接关系。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">依赖管理</strong>: 系统自动分析节点间的依赖，确保每个节点在其依赖的节点完成后执行。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">执行顺序构建</strong>: 系统基于依赖关系构建节点的执行顺序，防止循环依赖和执行死锁的发生。</section>
</li>
</ol>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">2.5 错误处理与资源管理</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 实现了完善的错误处理机制，确保节点执行过程中出现错误时，系统能够及时捕获并反馈给用户，避免系统崩溃。同时，ComfyUI 通过自动垃圾回收和内存管理功能，定期清理未使用的模型和缓存，优化系统资源使用。</p>
<p style="color: #000000;" data-tool="mdnice编辑器">常见的错误处理机制包括：</p>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">输入验证失败</strong>: 如果输入数据类型不匹配或数据缺失，系统会抛出异常。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">执行错误</strong>: 如果节点在执行过程中遇到错误，系统会捕获并将错误信息反馈到前端 UI。</section>
</li>
</ul>
<p style="color: #000000;" data-tool="mdnice编辑器">垃圾回收机制由以下代码管理：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;">gc_collect_interval = <span style="color: #d19a66;">10.0</span>  <span style="font-style: italic; color: #5c6370;"># 每10秒进行一次垃圾回收</span>
</code></pre>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">2.6 前端接口与用户交互</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 提供了 WebSocket 和 REST API 来管理前端与后端的通信。通过这些接口，前端 UI 可以实时监控节点的执行状态，并获取节点的执行结果。</p>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">WebSocket 通信</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">WebSocket 负责处理前端与后端的实时通信，包括节点执行状态的推送和执行命令的接收。</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #61aeee;">@routes.get('/ws')</span>
<span style="color: #c678dd;">async</span> <span style="color: #c678dd;">def</span> <span style="color: #61aeee;">websocket_handler</span>(request):
    <span style="font-style: italic; color: #5c6370;"># 处理前端连接</span>
    <span style="font-style: italic; color: #5c6370;"># 发送执行状态</span>
    <span style="font-style: italic; color: #5c6370;"># 处理节点执行</span>
</code></pre>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">REST API</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">REST API 用于获取节点信息和处理其他非实时请求。例如，可以通过以下 API 获取节点的详细信息：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #61aeee;">@routes.get("/object_info/{node_class}")</span>
<span style="color: #c678dd;">async</span> <span style="color: #c678dd;">def</span> <span style="color: #61aeee;">get_object_info_node</span>(request):
    <span style="font-style: italic; color: #5c6370;"># 获取节点信息</span>
</code></pre>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">2.7 系统扩展与自定义</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 的节点系统设计非常灵活，支持用户根据需求扩展新功能。用户可以通过编写自定义节点来扩展系统的功能，而无需修改核心代码。系统支持热插拔的节点加载机制，使得自定义节点的开发和调试更加便捷。</p>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">动态类型系统</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 支持动态类型系统，可以根据运行时情况自动调整节点的输入输出类型，确保系统的灵活性和可扩展性。</p>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">插件与自定义节点</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">自定义节点通过 <code style="color: #0e8aeb;">custom_nodes</code> 目录加载，用户可以编写自己的节点并通过 <code style="color: #0e8aeb;">NODE_CLASS_MAPPINGS</code> 注册到系统中。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">3. 缓存系统</span></h1>
<p style="color: #000000;" data-tool="mdnice编辑器">在 ComfyUI 项目中，缓存系统的设计主要由以下几个部分组成：</p>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">3.1 缓存类型</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 的缓存系统主要包括三种缓存类型：</p>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">outputs</strong>: 用于缓存节点的输出结果，减少不必要的重复计算。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">ui</strong>: 缓存与用户界面相关的数据，比如预览图像、状态信息等。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">objects</strong>: 专门用于存储大型对象，如模型等大体积数据。</section>
</li>
</ul>
<p style="color: #000000;" data-tool="mdnice编辑器">这三种缓存通过 <strong style="color: #0e88eb;"><code style="color: #0e8aeb;">CacheSet</code></strong> 类进行初始化和管理，具体实现如下：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">class</span> <span style="color: #e6c07b;">CacheSet</span>:
    <span style="color: #c678dd;">def</span> <span style="color: #61aeee;">__init__</span>(self, lru_size=None):
        <span style="color: #c678dd;">if</span> lru_size <span style="color: #c678dd;">is</span> <span style="color: #56b6c2;">None</span> <span style="color: #c678dd;">or</span> lru_size == <span style="color: #d19a66;">0</span>:
            self.init_classic_cache() 
        <span style="color: #c678dd;">else</span>:
            self.init_lru_cache(lru_size)
        self.all = [self.outputs, self.ui, self.objects]
</code></pre>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">3.2 缓存实现方式</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">缓存系统可以根据不同的需求选择不同的实现方式：</p>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">Classic Cache（传统缓存）</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">当没有设置 LRU 缓存大小时，缓存系统会初始化为经典的层级缓存。具体实现如下：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">def</span> <span style="color: #61aeee;">init_classic_cache</span>(self):
    self.outputs = HierarchicalCache(CacheKeySetInputSignature)
    self.ui = HierarchicalCache(CacheKeySetInputSignature)
    self.objects = HierarchicalCache(CacheKeySetID)
</code></pre>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">outputs</strong> 和 <strong style="color: #0e88eb;">ui</strong> 都使用 <code style="color: #0e8aeb;">CacheKeySetInputSignature</code> 作为缓存键，用于基于输入签名进行缓存。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">objects</strong> 使用的是 <code style="color: #0e8aeb;">CacheKeySetID</code> 作为缓存键，主要是为了存储大型对象，如模型数据。</section>
</li>
</ul>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">LRU Cache（最近最少使用缓存）</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">如果设置了 LRU 缓存大小，系统会初始化为 LRU 缓存，适用于内存较充足的情况下，以优化性能。</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">def</span> <span style="color: #61aeee;">init_lru_cache</span>(self, cache_size):
    self.outputs = LRUCache(CacheKeySetInputSignature, max_size=cache_size)
    self.ui = LRUCache(CacheKeySetInputSignature, max_size=cache_size)
    self.objects = HierarchicalCache(CacheKeySetID)
</code></pre>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">outputs</strong> 和 <strong style="color: #0e88eb;">ui</strong> 使用 LRU 缓存，能够根据使用频率自动淘汰最少使用的缓存项，保证内存得到最优管理。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">objects</strong> 仍然使用 <code style="color: #0e8aeb;">HierarchicalCache</code>，因为模型等大型对象的加载和卸载代价较高，不适合频繁淘汰。</section>
</li>
</ul>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">3.3 缓存键策略</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">为了确保缓存的正确性，缓存系统使用两种不同的缓存键策略：</p>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">CacheKeySetInputSignature</strong></p>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">用于 <strong style="color: #0e88eb;">outputs</strong> 和 <strong style="color: #0e88eb;">ui</strong> 缓存。</section>
</li>
<li>
<section style="color: #010101;">该缓存键基于节点的输入签名，包含节点类型、输入参数及祖先节点关系等，可以确保相同的输入得到相同的输出。</section>
</li>
</ul>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">CacheKeySetID</strong></p>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">用于 <strong style="color: #0e88eb;">objects</strong> 缓存。</section>
</li>
<li>
<section style="color: #010101;">这是一种基于节点 ID 和类型的简单缓存键，用于存储与输入无关的大型对象，如模型等。</section>
</li>
</ul>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">3.4 缓存清理机制</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">无论是传统缓存还是 LRU 缓存，ComfyUI 都提供了缓存清理机制，避免过多的缓存占用内存资源。</p>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">经典缓存清理</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">经典缓存会及时释放不再需要的数据，防止内存溢出。</p>
<p style="color: #000000;" data-tool="mdnice编辑器"><strong style="color: #0e88eb;">LRU 缓存清理</strong></p>
<p style="color: #000000;" data-tool="mdnice编辑器">LRU 缓存通过一个 <code style="color: #0e8aeb;">generation</code> 计数器和 <code style="color: #0e8aeb;">used_generation</code> 字典记录每个缓存项的使用时间。当缓存超出预设大小时，LRU 算法会移除最久未使用的项。</p>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">3.5 扩展性和调试</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 的缓存系统支持扩展和调试：</p>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">扩展性</strong>: 缓存系统支持自定义节点和模型的缓存策略，可以根据需要调整缓存大小和键生成逻辑。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">调试</strong>: 提供了 <code style="color: #0e8aeb;">recursive_debug_dump</code> 函数，能够以递归方式输出缓存的调试信息，方便开发者进行问题排查。</section>
</li>
</ul>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">def</span> <span style="color: #61aeee;">recursive_debug_dump</span>(self):
    result = {
        <span style="color: #98c379;">"outputs"</span>: self.outputs.recursive_debug_dump(),
        <span style="color: #98c379;">"ui"</span>: self.ui.recursive_debug_dump(),
    }
    <span style="color: #c678dd;">return</span> result
</code></pre>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">3.6 缓存小结</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 的缓存系统设计非常灵活，它通过 <code style="color: #0e8aeb;">CacheSet</code> 类将不同类型的数据（节点输出、UI 数据、大型对象）分离管理，并支持经典缓存和 LRU 缓存两种策略。依靠层次化的缓存结构和精确的缓存键策略，ComfyUI 能够有效地减少重复计算，并优化内存使用。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">4. 使用限制</span></h1>
<p style="color: #000000;" data-tool="mdnice编辑器">在使用 ComfyUI 时，虽然系统本身没有硬性限制节点数量，但仍然存在一些与性能、资源管理和系统稳定性相关的限制。这些限制大多与历史记录、图像分辨率、缓存、内存管理等方面有关。</p>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">4.1 历史记录限制</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 会保存工作流中的历史记录，用于回溯和调试。系统中对历史记录的最大保存数量有如下限制：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;">MAXIMUM_HISTORY_SIZE = <span style="color: #d19a66;">10000</span>
</code></pre>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">限制描述</strong>: 系统最多保存 10000 条历史记录。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">影响</strong>: 当历史记录达到上限时，旧的记录会被移除，无法继续回溯到更早的操作。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">建议</strong>: 如果不需要长时间保存历史记录，定期清理历史记录可以释放内存资源，提升系统的运行效率。</section>
</li>
</ul>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">4.2 图像分辨率限制</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 中对单个图像处理的最大分辨率有限制，防止超大图像导致系统崩溃或内存不足：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;">MAX_RESOLUTION = <span style="color: #d19a66;">16384</span>
</code></pre>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">限制描述</strong>: 系统允许的最大图像分辨率为 16384&#215;16384 像素。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">影响</strong>: 超过此分辨率的图像无法处理，可能会导致内存溢出或显存不足的情况。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">建议</strong>: 尽量避免处理过于高分辨率的图像，尤其是在显存较小的 GPU 上运行时。对于需要处理大图像的工作流，可以考虑将图像分割成多个较小的瓦片。</section>
</li>
</ul>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">4.3 VAE 解码瓦片大小限制</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">在图像生成过程中，VAE 解码器对瓦片大小进行了限制，以确保解码过程的效率与内存管理：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #98c379;">"tile_size"</span>: (<span style="color: #98c379;">"INT"</span>, {<span style="color: #98c379;">"default"</span>: <span style="color: #d19a66;">512</span>, <span style="color: #98c379;">"min"</span>: <span style="color: #d19a66;">128</span>, <span style="color: #98c379;">"max"</span>: <span style="color: #d19a66;">4096</span>, <span style="color: #98c379;">"step"</span>: <span style="color: #d19a66;">32</span>})
</code></pre>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">限制描述</strong>: VAE 解码时，允许的瓦片大小在 128 到 4096 像素之间。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">影响</strong>: 如果瓦片大小设置过大，系统可能会因为内存不足而运行缓慢或崩溃；瓦片大小过小则可能影响解码效率。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">建议</strong>: 根据 GPU 显存大小合理调整瓦片大小，找到性能和内存占用之间的平衡点。</section>
</li>
</ul>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">4.4 文件上传大小限制</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">在使用 ComfyUI 时，文件上传的大小受限于系统的配置，特别是通过命令行参数 <code style="color: #0e8aeb;">max_upload_size</code> 来控制：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;">max_upload_size = round(args.max_upload_size * <span style="color: #d19a66;">1024</span> * <span style="color: #d19a66;">1024</span>)
</code></pre>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">限制描述</strong>: 上传文件的最大尺寸由命令行参数 <code style="color: #0e8aeb;">max_upload_size</code> 控制，单位为 MB。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">影响</strong>: 超过上传文件大小限制的文件将无法上传或处理。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">建议</strong>: 如果需要上传较大的文件，可以通过调整命令行参数来提高上传文件的大小限制。</section>
</li>
</ul>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">4.5 缓存限制</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 使用缓存系统来优化计算效率，减少重复计算。缓存的大小和管理方式可以通过 LRU（最近最少使用）策略进行控制：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">def</span> <span style="color: #61aeee;">__init__</span>(self, lru_size=None):
    <span style="color: #c678dd;">if</span> lru_size <span style="color: #c678dd;">is</span> <span style="color: #56b6c2;">None</span> <span style="color: #c678dd;">or</span> lru_size == <span style="color: #d19a66;">0</span>:
        self.init_classic_cache() 
    <span style="color: #c678dd;">else</span>:
        self.init_lru_cache(lru_size)
</code></pre>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">限制描述</strong>: 缓存的大小受 LRU 策略控制，缓存过多时，系统会淘汰最少使用的缓存项。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">影响</strong>: 当缓存大小设置过小，可能会频繁清除缓存，导致重复计算；缓存过大则可能占用大量内存。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">建议</strong>: 根据内存资源合理设置缓存大小。对于内存充足的系统，可以增加缓存大小，以减少重复计算；对于内存紧张的系统，建议定期清理缓存。</section>
</li>
</ul>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">4.6 执行队列限制</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">节点的执行通过队列进行管理，系统按顺序执行节点，避免同时执行过多节点造成性能瓶颈：</p>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">限制描述</strong>: 系统使用队列调度节点执行，包括当前运行的队列和等待执行的队列。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">影响</strong>: 如果节点过多，执行速度会受到队列调度的影响，可能出现等待时间过长的情况。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">建议</strong>: 尽量简化工作流，避免过多的节点同时排队执行。如果遇到性能瓶颈，可以将大规模的工作流分解为多个子工作流逐步执行。</section>
</li>
</ul>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">4.7 Tokenizer 限制</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">在文本处理方面，CLIP 模型的 Tokenizer 有一个最大长度限制：</p>
<pre style="color: #000000;" data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #98c379;">"model_max_length"</span>: <span style="color: #d19a66;">77</span>
</code></pre>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">限制描述</strong>: CLIP 模型的 Tokenizer 最多支持 77 个 token。可修改配置。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">影响</strong>: 超过 77 个 token 的输入文本将被截断，可能会影响文本生成的精度。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">建议</strong>: 尽量保持输入文本简洁，避免过长的描述。如果必须使用长文本，可以通过分段输入的方式进行处理。</section>
</li>
</ul>
<h2 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">4.8 限制小结</span></h2>
<p style="color: #000000;" data-tool="mdnice编辑器">虽然 ComfyUI 对节点数量没有明确的硬性限制，但在使用过程中仍然受到一些系统资源和配置的限制。这些限制大多是为了确保系统的稳定性、优化性能以及合理使用内存资源。为了避免因这些限制导致的性能瓶颈或崩溃，建议在使用时遵循以下最佳实践：</p>
<ul class="list-paddingleft-1" style="color: #000000;" data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">保持工作流简洁</strong>: 避免不必要的节点，定期清理历史记录和缓存。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">监控系统资源</strong>: 注意监控内存和显存的使用情况，避免资源耗尽。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">分解大型工作流</strong>: 对于复杂的工作流，可以分成多个子工作流来逐步执行，减少单次执行的节点数量。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">调整系统配置</strong>: 根据实际需求调整文件上传大小、缓存设置等参数。</section>
</li>
</ul>
<p style="color: #000000;" data-tool="mdnice编辑器">通过合理地管理工作流和系统资源，ComfyUI 可以在大型工作流中保持高效运行，避免因资源限制导致的性能问题。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">模块化设计带来的无限可能</span></h1>
<p style="color: #000000;" data-tool="mdnice编辑器">ComfyUI 的模块化节点系统不仅提升了用户的易用性，还通过灵活的扩展机制和高效的缓存管理提供了强大的自定义能力。</p>
<p style="color: #000000;" data-tool="mdnice编辑器">它的图形化工作流设计大大降低了 AI 绘图的技术门槛，使得更多用户能够轻松上手，并根据自己的需求定制不同的图像生成方案。</p>
<p style="color: #000000;" data-tool="mdnice编辑器">随着社区的不断壮大和功能的持续扩展，ComfyUI 有望成为 AI 绘图领域的重要基础设施之一，为创作与开发者提供无限的可能性。</p>
<p style="color: #000000;" data-tool="mdnice编辑器">以上。</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2024/11/comfyui-node-system-source-analysis/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
