pinned memory 上限:Windows 和 Linux 不一样
ComfyUI 启动时会在控制台刷一串状态行,大多数人只盯着 Total VRAM ... MB, total RAM ... MB 和 Set vram state to: ... 这两行。但在 v0.31.0(2026-08-08)的源码里,还有一行同样值得看:
Enabled pinned memory {}
后面那个数字的单位是 MB,它是算出来的上限,不是你机器的内存总量,也不是一个写死的常量。更麻烦的是:Windows 和其它系统走的是两套完全不同的计算式。同一台机器装成 Windows / Linux 双系统,这行数字按源码就应该不一样。所以拿两台不同系统的机器对着启动日志比数字、然后怀疑自己哪里配置错了,方向从一开始就是歪的。
先把话说清楚:本文讲的是 comfy/model_management.py(v0.31.0,2026-08-08)里这段常量的计算逻辑,全部是源码口径。pinned memory 本身是 CUDA 语境里常说的页锁定内存,这里不展开它的底层原理,只讲 ComfyUI 怎么给它定上限、以及你该据此做什么判断。
两套公式,原样摆出来
源码里 MAX_PINNED_MEMORY 的初始值是 -1,判定规则是 <= 0 时视为不启用。也就是说,这个变量在被赋值之前,pinned memory 这条路径根本不参与工作。随后按平台分叉:
| 平台 | MAX_PINNED_MEMORY 的取值 |
|---|---|
| Windows | ram * 0.40 |
| 其它系统 | max(ram * 0.40, min(ram * 0.90, ram - 4 * 1024**3, ram + get_disk_swap_total() - 16 * 1024**3)) |
Windows 那一行旁边跟着一句源码注释,原文是:
# Windows limit is apparently 50%
这里有个很容易读错的地方:注释写的是 50%,代码取的是 0.40。注释里的 apparently(看起来是、据说是)本身就是一个不确定语气,它记录的是作者对系统侧限制的理解,而代码里的 0.40 才是实际执行的那个数。看源码时把注释当成代码语义来引用,是踩坑的经典方式——这里请一律以 0.40 为准。至于为什么写 50% 却取 40%,源码没有解释,我们也不替它编一个理由;你只需要记住:注释里的 50% 不是任何一处代码会用到的数,看到别人拿「Windows 是 RAM 的一半」来算预期值,那是读注释读出来的,不是代码行为。
非 Windows 那个嵌套怎么读
非 Windows 的式子看着长,拆成两层就清楚了。
外层是一个 max(ram * 0.40, ...),作用是地板。 不管里层算出什么结果,最终不会低于内存总量的 40%。这一点和 Windows 的取值恰好对齐——40% 是两边共同的下限,差异全在非 Windows 这边有机会往上抬。
内层是一个三项 min,作用是封顶,谁小听谁的:
ram * 0.90:不管别的条件多宽松,最多也就到内存总量的 90%。ram - 4 * 1024**3:内存总量减 4GB,给系统和其它进程留的固定余量。ram + get_disk_swap_total() - 16 * 1024**3:把磁盘 swap 总量算进来之后再减 16GB。
第三项是这套公式里唯一和 Windows 不同的思路:非 Windows 系统认为「物理内存 + swap」才是可周转的总盘子,然后从这个总盘子里扣掉 16GB 作为安全垫。
按公式做几组纯算术推演,就能看出哪一项会成为决定因素(下面只是把上面的式子代入数字,不涉及任何实机数据):
- 32GB 内存、没有 swap:内层三项分别是 28.8GB、28GB、16GB,取最小得 16GB;外层地板是 12.8GB,最终为 16GB。决定因素是第三项。
- 32GB 内存、配了 64GB swap:三项是 28.8GB、28GB、80GB,取最小得 28GB;最终为 28GB。这时候第三项被 swap 撑上去了,反而是「内存减 4GB」那一项卡住了上限。
- 8GB 内存、没有 swap:三项是 7.2GB、4GB、−8GB,取最小是个负数;这时候外层
max的地板生效,最终为 3.2GB。这就是为什么要套一层max——否则小内存无 swap 的机器会直接算出一个负数或极小值。
判断依据落在这里:如果你在 Linux 上想让 pinned memory 的上限高一点,能动的杠杆其实只有物理内存和 swap 总量两个;而当 swap 已经足够大之后,继续加 swap 也不会再抬高上限,因为 ram * 0.90 和 ram - 4GB 会先一步封顶。反过来,一台无 swap 的小内存机器,最终落在 40% 地板上,这是设计如此,不是配置出错。
v0.31.0 才修的那件事
上面这套「把 swap 算进来」的逻辑不是一直都在。v0.31.0(2026-08-08)的 release note 写的是:Don’t pin too much memory on Linux systems with no swap partition.(PR #15266)。
这条信息的用法很直接:如果你在 Linux 上跑 ComfyUI、系统没有 swap 分区、而且版本早于 v0.31.0,那么「pin 的内存过多」是官方在 2026-08-08 才处理的一个已知问题,你遇到的内存吃紧不一定是你自己的工作流或自定义节点造成的。先确认自己跑的到底是哪个版本(对着仓库的 release notes 核一下本地代码是不是已经包含 PR #15266),再决定要不要继续往下查——这比一上来就删节点、改工作流要省事。
这里顺带提醒一个判断顺序上的坑:v0.31.0 是 2026-08-08 发布的,而它的前一个版本 v0.30.0 是 2026-08-03,中间只隔了五天。也就是说,如果你是在这五天里更新的 ComfyUI,你手上很可能正好是那个「已经走 pinning 路径、但还没有无 swap 保护」的版本区间。这种窄窗口版本最容易被忽略,因为它看起来「已经是最新的了」。
两条 swap 告警,各自说明什么
源码里还有两条和这块直接相关的告警文案,原文是:
Could not get amount of swap memory on system.
Could not read Windows swap usage; falling back to RAM-pressure pin eviction: %s
这两条不是错误,值得单独说,因为它们长得很像出了事。
第一条对应的是拿不到系统 swap 总量。回到上面的公式你就明白它为什么重要:get_disk_swap_total() 正是内层 min 第三项的输入,读不到这个值,那一项的计算前提就没了。
第二条更有意思——它出现在 Windows 上,而 Windows 的上限公式(ram * 0.40)里根本没有 swap 这一项。也就是说,Windows 侧读 swap 使用量不是为了算上限,而是为了做 pin 的淘汰决策;读不到时的兜底行为,告警文案自己写明了:falling back to RAM-pressure pin eviction,退回按 RAM 压力来淘汰已 pin 的内存。
结论就一句:看到这两条别急着修。 源码为它们准备了明确的降级路径,说明「拿不到 swap 信息」是官方预期内的情况。如果你的实际症状(比如加载卡住、内存吃满)和这两行同时出现,它们更可能是同一批环境因素的两个表现,而不是因果关系。
什么时候该动 --disable-pinned-memory
关掉它的开关是 --disable-pinned-memory(flag,不带值)。默认情况下它是不加的。
要不要加,先问自己一个问题:你是在解决一个具体症状,还是在「预防性调优」? 如果是后者,建议不加——你把它关掉之后,v0.30.0 引入的那套「用 pinning 基础设施、以 MRU 策略把权重加载到进程 RAM」(PR #15027)的加载路径就不再按原设计工作,而你并没有拿到任何可衡量的收益。
如果是前者,社区侧有一条可参考的线索,注意它的性质:官方仓库 issue #13730 反映了「LTX 2.3 FP8/Q4KM 在 RX 7900 XTX + ROCm 上会在 Requested to load LTXAV 处停住,除非把 dynamic VRAM / pinned memory / async offload 关掉」这一现象,创建于 2026-05-06,标签为 Potential Bug,截至 2026-08-09 仍为 open。
这里必须说清三件事:Potential Bug 的字面含义是「疑似 bug」,不等于官方已确认;issue 处于 open 状态,不等于已修复;我们没有读过这条 issue 的正文与评论,所以上面这段只复述了它的标题所写的内容,任何复现步骤、根因分析、社区变通方案,本文都不提供。
它对你的实际价值在于:它把「关掉 pinned memory」放进了一个具体的组合里——dynamic VRAM、pinned memory、async offload 三者是一起被提到的。如果你要做排查,合理的做法是把这三个开关当成一组来二分,而不是只单独关一个 pinned memory 然后认定问题解决或没解决。对应的开关分别是 --disable-dynamic-vram、--disable-pinned-memory、--disable-async-offload。
# 排查用,不是推荐的日常启动配置
python main.py --disable-pinned-memory --disable-async-offload --disable-dynamic-vram
以上为按官方参数语义组合的示例,未逐项实测,以官方文档与 --help 输出为准。排查结束后请把不必要的开关去掉——每关一个,你都是在退出一条官方默认走的优化路径。
怎么确认这行东西到底生效没有
给三个可执行的动作,按顺序做:
- 看启动日志里有没有
Enabled pinned memory这一行。 没有这一行,说明 pinned memory 没启用(回到那个-1:<= 0视为不启用),后面的所有推理都不用做了。 - 有这一行的话,把后面的 MB 数和上面两套公式对一下。 你只需要知道自己的内存总量(
Total VRAM ... MB, total RAM ... MB这一行会打印)和平台,就能算出预期值。对不上,说明你对自己所在平台的判断错了,或者版本不是 v0.31.0。 - 在 Linux 上对不上、且数字明显偏大时,先查版本和 swap 配置,参照 PR #15266 那一条。
什么情况说明和 pinned memory 无关
这一节比前面几节更重要,免得一条道走到黑。
- 启动日志根本没有
Enabled pinned memory这一行:没启用的东西背不了锅,往别处查。 - 症状是显存(VRAM)不够而不是内存(RAM)吃紧:pinned memory 的上限公式的输入全部是
ram和 swap,和显存没有任何关系。显存侧有另一套常量(保留显存在 Windows 上是 600MB、其它系统 400MB),和本文这套是两码事。 - 日志里出现
Potential memory leak detected with model ...或WARNING, memory leak with model ...:官方文案给的方向是「避免循环引用 / 检查谁还在引用它」,这几乎总是指向自定义节点持有了模型引用。此时该做的是用--disable-all-custom-nodes做二分排查,而不是调内存上限。 - 你在 Windows 上,且症状随 swap(页面文件)设置变化:Windows 的上限公式里没有 swap 项,页面文件只影响 pin 淘汰策略。如果你观察到的现象强依赖页面文件大小,那大概率不是这条上限造成的。
把这几条排掉之后再回头看 pinned memory,命中率会高很多。这一块的整体设计思路其实很朴素:给一个不会太离谱的上限,拿不到信息时退回一个保守策略。真正反直觉的只有两点——注释里的 50% 和代码里的 0.40 不是一个数,以及非 Windows 那个 max 的地板值恰好和 Windows 的唯一取值相同。记住这两点,日志那行数字就不再神秘了。
延伸阅读
- ComfyUI 的五个注意力实现参数怎么选,以及 xformers 在里面扮演什么角色
- 读懂 DynamicVRAM:它什么时候会被悄悄关掉
- ComfyUI 五种缓存模式该选哪个:从
--cache-ram的默认阈值倒推启动参数
本文依据 ComfyUI 官方仓库(github.com/Comfy-Org/ComfyUI)的 README、comfy/cli_args.py、
release notes 与官方安全公告整理,核对日 2026-08-09,对应版本 v0.31.0;
文中引用的 issue 状态为该日期的快照。本文内容为官方文档与源码口径,非本机实测。
参数、默认值与功能随版本变动,请以官方文档与 python main.py --help 的实际输出为准。