Linux 7.3 在 VRAM 不足时提升性能
摘要
Linux 7.3 合并了改进 VRAM 管理的内核补丁,通过优化内存淘汰和缓存机制,在物理 VRAM 不足时增强了游戏性能稳定性。
暂无内容
查看缓存全文
缓存时间: 2026/08/18 10:28
# VRAM管理第二部分:超越物理显存的限制
来源:https://pixelcluster.dev/VRAM-Overcommit/
2026年8月17日
今年早些时候,我曾撰写关于改善游戏显存管理工作的博文(https://pixelcluster.dev/VRAM-Mgmt-fixed)。如今,经过数月的邮件列表讨论,内核补丁最终被合并到上游并计划纳入Linux 7.3版本!太棒了!
为庆祝这一进展,让我们深入探讨前文中这句话的含义:
> (游戏)应该运行得更加稳定——只要游戏本身不使用超过你实际拥有的显存。
那么问题来了:如果游戏确实需要更多显存,会发生什么?
对此的普遍预期是,一旦出现这种情况,游戏体验将彻底崩溃。游戏频繁崩溃,性能暴跌至无法游玩的程度,良好的游戏体验变得遥不可及。
但这真的是无法避免的必然现象吗?究竟是什么导致显存耗尽时体验如此糟糕?更重要的是:我们如何尽可能减轻这种影响?
## 预期管理
理论上,显存耗尽应仅影响性能而非稳定性。自GPU驱动诞生之初就支持显存过量使用:当驱动过量分配显存时,通常允许你申请任意数量的显存,实际分配量取决于内核驱动在物理显存中的调度空间。
从性能角度看,显存耗尽导致性能下降的根本原因很简单:一旦游戏请求的显存超过物理容量,部分游戏内存将被迫迁移/换出至CPU内存。对GPU而言,访问CPU内存的速度远慢于显存:不仅CPU内存本身慢于独立显存,所有内存访问还需通过PCIe总线传输。PCIe总线增加延迟,且在从CPU内存读取时通常成为带宽瓶颈。
由于PCIe速度限制,过量使用显存必然存在无法规避的性能瓶颈。假设GPU通过PCIe 4.0x16连接,理论带宽略低于32GiB/s。PCIe总线每毫秒可传输约32.2MiB数据。对于30FPS的最低帧率(每帧33.3ms),GPU每帧可访问的最大数据量约为1,075.5MiB,刚超过1GiB。换言之,若单帧需从换出内存中调取超过1GiB数据,则30FPS的目标将彻底无法实现。
## 并非所有内存地位平等
同时,GPU读取少量CPU内存并不必然导致性能崩塌。实际上,GPU驱动有时甚至会在显存充足时将命令缓冲区等数据存放在CPU内存!尽管GPU执行这些命令时需访问CPU内存,系统仍运行良好。为何这些访问不受影响——而显存耗尽却显得如此灾难性?[1](https://pixelcluster.dev/VRAM-Overcommit/#fn:1)
缓存机制显著影响这一权衡过程。由于缓存命中时的访问延迟与缓存数据存放在CPU或GPU内存无关,PCIe总线的高初始延迟可通过缓存命中(在一定程度上)摊销。我们通过编写微基准测试,使用对抗性访问模式最小化缓存命中率,可测量不同缓冲区大小的访问延迟来估算CPU内存与显存的延迟差异。RDNA3架构的测试结果如下:
RDNA3 GPU的缓存微基准测试。
正如预期,当缓冲区大小不超过L2缓存容量时,CPU内存与显存的访问延迟完全一致,因为数据直接从缓存获取。但在6MB(RDNA3的L2缓存容量)边界处,CPU内存延迟飙升至约2400周期/访问,而设备内存延迟仍保持相近水平。注意显存访问也经过Infinity缓存,但CPU内存访问则不经过(L2未命中时直接访问PCIe)。这是因为Infinity缓存位于显存之上,任何未命中显存的访问自然无法触及Infinity缓存。
显然内存初始状态未缓存,首次访问仍存在显著延迟。失去Infinity缓存同样影响显著:PCIe访问延迟约为Infinity缓存命中的7.3倍,显存访问延迟的4.6倍。如此高的延迟需要极高的缓存命中率才能完全摊销PCIe访问成本。这意味着仅少数应用场景中,CPU内存的微小性能损耗会让你在显存可用时主动选择使用它。当显存内存被换出时,性能下降几乎不可避免。
尽管如此,即使性能下降不可避免,不同内存类型的换出影响程度各异。高度缓存友好的内存受CPU内存延迟影响较小。即使访问模式不友好但访问频率低,由于GPU很少需要从CPU内存调取数据,性能影响也可能有限。许多内存分配中,GPU可能仅访问总分配量的极小部分,其余部分从未读取。若这些分配被换出,虽可能涉及数GiB数据,但实际每帧访问量仍远低于1GiB硬性上限。
所有这些变量使得预测实际换出时的性能表现异常复杂。但简而言之:根据被换出内存的访问频率与缓存效率,你或许能在显存耗尽时仍保持(非完全)性能稳定!
## 面对现实
我们通过理论推演已构建出高性能显存过量使用的蓝图。太好了!现在启动SteamOS,运行游戏并调高设置——`radv/amdgpu: Not enough memory for command submission.`
哦。
事实证明,实践中显存耗尽确实会引发大量稳定性问题。
此错误与常规的“分配失败,内存不足”错误有所不同。注意错误信息明确指向命令提交:RADV驱动在内核返回`-ENOMEM`时会打印此消息[2](https://pixelcluster.dev/VRAM-Overcommit/#fn:2),但单纯提交命令并不会分配新资源!所有命令缓冲区均提前分配成功,尽管所有内存都已成功分配,但在GPU提交时突然出现“内存不足”错误。
是时候展开新一轮内核探索了!让内核接受提交应该不难——毕竟内核已接受所有内存分配[3](https://pixelcluster.dev/VRAM-Overcommit/#fn:3)!
## 内核锁机制的恐怖
`amdgpu`驱动在每次提交时必须确保GPU命令可能引用的所有内存均可访问。随着现代图形API广泛采用无绑定设计,必须假设所有已分配内存都可能被引用。因此`amdgpu`需确保所有已分配内存保持可访问状态。
每个内存分配携带其可被正确访问的内存类型信息(此处指系统内存或GPU显存)。多数分配可被CPU内存或显存访问,`amdgpu`对两种类型均接受。但部分分配必须仅存在于显存中。若这些内存分配因其他应用申请显存而被换出至系统内存,`amdgpu`必须将其移回显存。由于可用显存完全耗尽,移回操作需换出其他内容。出于某种原因,此操作失败并报告内存不足状态。
为解释为何换出操作会随机失败,需短暂切入内核对GPU分配的(CPU侧)锁机制处理。要换出内存分配,必须获取该分配关联的锁。但在提交过程中,还需锁定所有被引用的分配,防止其他应用在准备GPU工作时移动分配。若另一GPU提交并发执行相同操作,可能出现如下死锁:
并发提交引发死锁
若提交A试图换出已被提交B锁定的分配,而提交B又需锁定提交A的分配以继续执行,便形成经典的ABBA死锁条件。
但无需担心,内核具备检测和解决死锁的能力!死锁检测机制的详细说明见内核文档[链接](https://www.kernel.org/doc/html/latest/locking/ww-mutex-design.html),简而言之,内核将锁操作关联至“事务”(主要用于追踪已获取的锁)。若两个事务可能死锁,其中一个事务将被标记为“受损”,下次尝试获取锁时会返回`-EDEADLCK`错误。此错误要求事务中止:释放事务期间获取的所有锁,并重新开始执行。在命令提交上下文中,这意味着驱动将重新遍历所有内存分配并确保其可访问。
那么陷阱在哪里?实际上并无陷阱。该机制运行稳健可靠。
但前提是必须在所有位置正确实现。
在图形子系统中,wound-abort-retry循环的底层细节通过`drm_exec`辅助库抽象。无需手动跟踪已锁定分配及`-EDEADLCK`时的锁释放,只需使用`drm_exec_lock_obj`辅助函数即可。若研究TTM(共享Linux GPU内存管理层)中的锁机制[代码](https://elixir.bootlin.com/linux/v7.2-rc7/source/drivers/gpu/drm/ttm/ttm_bo_util.c#L841-L867),会发现其中严重缺乏`drm_exec`的应用。
实际上甚至有注释说明`-EDEADLCK`会导致换出失败。问题根源在此!一旦因命令提交时的内存压力触发此死锁条件,内核将直接拒绝提交而非重试。
已有补丁集尝试在TTM中集成`drm_exec`辅助函数,早在2024年[已提交](https://lore.kernel.org/all/[email protected]/),但因部分未解决的缺陷未能合并。我的任务艰巨:将补丁集适配至当前内核版本并定位那些残留缺陷。
补丁集适配过程相对顺利,仅耗时一周高强度游戏测试(游戏在显存激烈竞争时随机挂起3分钟)。尚可接受!
我尝试[重发](https://lore.kernel.org/all/[email protected]/)修复所有缺陷的补丁集,希望此次能够合并,但在此之前仍有大量工作需要完成。
既然显存耗尽至少不再导致应用随机崩溃,我们终于可以放心调整设置并观察性能。初始结果呈现出如此壮观的性能曲线:
糟糕的性能图表
## 等等性能去哪儿了
要查明性能低下的原因,需定位系统实际执行的耗时操作。对于“内核驱动到底在做什么?”这类宏观问题,我推荐使用[gpuvis](https://github.com/mikesart/gpuvis)。该工具利用内核跟踪点构建事件时间线(包括“GPU工作提交开始/结束”,可推算每次提交耗时)。
通过分析显存耗尽时的跟踪数据,gpuvis显示如下场景:
SDMA长时间繁忙导致的糟糕性能
原来,大部分时间并未用于处理提交(即gfx_0.0.0活动),而是为准备提交而移动内存(sdma0活动)!
使用gpuvis的事件列表配合过滤器,仅显示特定缓冲区对象的移动事件(我随机选取一个,多数对象具有相似模式),即可更清晰地看出:
gpuvis事件过滤器展示缓冲区乒乓效应
列表清晰显示争用进程(本例中为`gamescope`和游戏本身)持续交替换出与移回相同内存块。这非常糟糕!且与我在首篇博文中描述的现象高度相似:
> 通常,两个竞争应用会大致轮流执行GPU工作——先由应用A提交,然后应用B,再回到A,依此类推。这种方式会导致内存随每次提交不断往复移动。应用A被踢出后立即移回,挤掉应用B(后者在下一次移回内存)。所有这些移动最终导致**更差**的性能——甚至不如从不移动内存。
此描述针对早期因显存分配过于激进而导致的内存乒乓现象。该问题已通过“当显存无余量时停止申请”机制解决,而内核仅在我实现基于dmem cgroup的显存保护后才重新表现出适度激进性。显然,此机制无意中重现了乒乓现象。
理论上,dmem cgroup显存保护设计不应导致内存乒乓,因为内核只应换出无cgroup显存保护的内存。正常情况下,不应被允许换出受保护的显存。
此规则的唯一例外是必须存在于显存中才能维持系统稳定性的内存。这类内存分配始终允许移入显存以确保系统稳定性。通常应用中几乎没有内存必须存在于显存才能正确运行,但来自应用的缓冲区对象例外:包含要扫描输出到显示器的图像数据[4](https://pixelcluster.dev/VRAM-Overcommit/#fn:4)。
相似文章
Linux 7.2 版本
Linux 7.2 已发布,带来了重大改进,包括缓存感知调度、Raspberry Pi GPU 的电源管理以及各种性能提升。
AI增强的Linux 7.2带来缓存感知调度
Linux内核7.2已发布,带有AI增强的安全修复和新的缓存感知调度,以改善处理性能。
AMD通过Linux 7.4为Radeon iGPU提升AI与LLM性能高达18~23%
AMD借助Linux 7.4的优化,将Radeon集成显卡的AI和大型语言模型性能提升18-23%。
新版AMD Linux补丁提升Steam Deck低端游戏性能
新版AMD Linux内核补丁通过监控核心负载并临时将繁忙核心设置为性能模式,提升了Steam Deck低端游戏性能,在《文明VI》中1%低帧率最高提升31.8%。
Linux 7.1
Linux kernel 7.1 已在内核邮件列表上宣布,标志着新主版本发布,预计将带来功能更新和改进。