
1. 128G 统一内存跑五个模型的真实动机把五个模型塞进一台 128G 统一内存的机器里同时跑这个念头最早来自一次很尴尬的演示。当时我在本地同时开着对话模型、图像生成、语音转写和两个小参数量的推理任务结果显存监控曲线像过山车一样前一个模型刚加载完后一个就把前一个挤出去了。那种加载-卸载-再加载的循环单次看着只是几秒累积起来能把一次完整工作流拖到无法忍受。统一内存这个概念在消费级设备上真正被重视也就是最近两三年的事。它的核心价值在于 CPU 和 GPU 共享同一块物理内存池不需要像传统独显那样把数据在系统内存和显存之间来回拷贝。128G 的容量意味着你可以同时驻留多个中等规模的模型而不必反复做权重搬运。但能装下和能跑好之间隔着一条很深的沟我踩的七个坑基本都在这条沟里。这篇文章面向的是已经在本地折腾模型部署、手里有统一内存设备或者正在考虑入手这类硬件的从业者。我会把管理器的设计思路、七个坑的完整排查链路、以及每个坑背后的原理讲清楚。你不需要有很深的底层经验但至少要跑通过一次 GGUF 模型的加载知道 ComfyUI 的工作流大概长什么样。如果你正在纠结到底要不要上统一内存或者为什么我的模型总是爆显存这篇内容应该能帮你省下不少试错时间。先说结论性的判断统一内存不是万能药它解决的是容量问题不解决带宽问题。五个模型同时驻留是可行的但同时高负载运行是另一回事。我的管理器最终做到了让五个模型和平共处代价是在调度策略上做了大量妥协。下面从管理器本身讲起。2. 管理器到底管什么从显存监控到模型生命周期2.1 为什么现成方案不够用一开始我试过用现成的模型服务框架来管这些模型但很快发现它们的设计假设和统一内存场景不匹配。大多数框架默认你有独立的显存加载模型时直接往 GPU 上怼显存不够就报错。统一内存的特点是看起来显存很大但实际可用带宽和延迟跟独显不是一回事框架的默认策略会导致内存被占满但利用率极低。我需要的是一个能感知统一内存特性的调度层核心职责有三个监控实际内存压力、决定模型的加载和卸载时机、在多个模型请求之间做优先级仲裁。这三个职责听起来简单但每一个都有坑。2.2 管理器的核心模块拆解管理器我分成了四个模块用 Python 写的因为要跟现有的推理框架对接。第一个是内存探针模块。它定期读取系统的内存统计信息包括已用内存、缓存、以及 GPU 侧可见的可用内存。这里有个关键细节统一内存下系统内存和 GPU 可见内存是同一块物理内存的不同视图你不能简单地把两者相加。我最初就犯了这个错误导致内存预算算出来是实际的两倍模型加载直接 OOM。第二个是模型注册表。每个模型注册时记录它的文件路径、量化格式、加载后的预估内存占用、以及一个热度权重。预估内存占用不能只看文件大小GGUF 格式的模型在加载后会有额外的开销包括 KV cache、计算图缓冲区等。我的经验值是文件大小的 1.15 到 1.3 倍具体取决于上下文长度和批大小。第三个是调度器。它维护一个模型状态机未加载、加载中、已加载空闲、已加载运行中。调度器根据请求的优先级和当前内存压力决定是否加载新模型、是否卸载空闲模型。这里的策略我改了好几版后面会详细讲。第四个是卸载执行器。负责真正把模型从内存里释放掉包括清理 KV cache、释放计算图、以及通知推理框架做资源回收。这个模块最容易出问题因为不同框架的释放接口行为不一致有的释放不彻底有的释放后还有残留引用。2.3 内存预算的计算逻辑统一内存下做预算核心是搞清楚可用内存到底是多少。我的做法是读取系统总内存减去操作系统和常驻服务的基础占用得到一个基线值读取 GPU 侧报告的可分配内存上限取两者中较小的那个作为硬上限再留出 15% 的安全余量防止突发分配导致 OOM这个 15% 不是拍脑袋定的。我实测过在模型加载和推理切换的瞬间内存分配会有尖峰留太少会触发 OOM killer留太多又浪费容量。15% 是在我的负载模式下比较稳的值你的场景可能需要调整。注意统一内存的可用内存是动态变化的缓存回收、后台进程、甚至文件系统的页缓存都会影响它。管理器必须持续监控不能只在启动时算一次。3. 七个坑的完整排查链路这一节是重点。七个坑我按踩到的顺序讲每个坑都给出当时的现象、排查过程、根因和修复方案。你如果遇到类似现象可以直接对照排查。3.1 第一个坑GGUF 加载后内存占用远超预期现象很直接一个 7B 的 Q4 量化模型文件大小约 4.2G加载后内存占用飙到 9G 以上。我一开始以为是量化格式的问题换了几个不同的 GGUF 文件现象一致。排查过程先用内存分析工具抓了加载前后的内存快照发现大头不在权重本身而在两个地方。一是 KV cache 的预分配框架默认按最大上下文长度预分配7B 模型在 32K 上下文下KV cache 能占到 3G 以上。二是计算图的中间缓冲区推理框架为了性能会预分配一批临时缓冲区。根因GGUF 文件大小只反映权重不反映运行时开销。KV cache 的大小跟上下文长度、层数、注意力头数都相关粗略估算公式是2 * 层数 * 上下文长度 * 隐藏维度 * 精度字节数。这个开销在长上下文场景下非常可观。修复方案在管理器里给每个模型单独配置 KV cache 上限不按最大上下文预分配而是按实际请求动态增长。同时把计算图缓冲区也纳入预算。改完之后同一个模型的内存占用降到了 6G 左右。3.2 第二个坑ROCm 环境下的内存统计口径不一致这个坑比较隐蔽。我在 ROCm 环境下跑管理器发现系统工具报告的内存占用和推理框架自己报告的对不上差了将近 2G。管理器按系统工具的数据做调度结果就是明明还有余量却不敢加载新模型或者反过来以为有余量结果加载失败。排查过程对比了三个数据源——系统内存统计、ROCm 的显存查询接口、推理框架的内部统计。发现 ROCm 的查询接口在统一内存模式下返回的是可分配上限而不是实际可用而系统统计里包含了大量可回收的页缓存。根因不同工具对内存占用的定义不一样。系统统计把页缓存算作已用但页缓存是可以随时回收的ROCm 接口返回的是理论可分配值没有扣除实际已分配的部分。修复方案管理器统一以实际已分配且不可回收的内存作为调度依据。具体做法是读取系统统计后减去可回收的缓存部分再和 ROCm 接口的数据做交叉验证取更保守的值。这个逻辑写起来不复杂但需要你对系统的内存管理机制有基本了解。3.3 第三个坑模型卸载不彻底导致内存泄漏现象是跑了一段时间后内存占用只增不减即使所有模型都标记为已卸载内存也回不到初始水平。每次加载卸载循环大概泄漏 200M 到 500M。排查过程用内存分析工具做了几轮加载卸载对比每次循环后的内存快照。发现泄漏的对象主要是计算图和一部分张量缓冲区它们被推理框架的内部缓存持有没有随模型卸载一起释放。根因推理框架为了加速重复加载会缓存一部分编译好的计算图和缓冲区。这个缓存在正常使用下是好事但在需要频繁切换模型的场景下就成了负担。修复方案在卸载执行器里显式调用框架的缓存清理接口并且定期做一次强制回收。另外我把模型的加载卸载频率降下来尽量让模型常驻而不是频繁切换。这个改动配合调度策略的调整泄漏问题基本消失。3.4 第四个坑ComfyUI 工作流与模型管理器的资源竞争ComfyUI 是我工作流里的重要一环但它有自己的模型加载和显存管理逻辑。当 ComfyUI 和管理器同时想加载模型时资源竞争就出现了。最典型的现象是 ComfyUI 加载图像模型时把管理器刚加载好的对话模型挤掉然后对话请求过来又要重新加载来回震荡。排查过程观察了两个系统的加载时序发现它们各自独立做决策没有协调机制。ComfyUI 在开始一个工作流时会预加载它需要的所有模型而管理器不知道这个预加载计划。根因两个系统缺乏资源协调。ComfyUI 的设计假设是它独占 GPU 资源而管理器的假设是它可以调度所有模型。修复方案我给管理器加了一个外部预留接口ComfyUI 在开始工作流前先向管理器申请预留内存管理器根据当前压力决定是否批准以及是否需要先卸载其他模型腾出空间。这个接口实现起来需要改一点 ComfyUI 的启动脚本但效果很好震荡问题解决了。3.5 第五个坑低显存模式下的调度策略失效我试过在显存更紧张的环境下跑同样的管理器发现原来的调度策略完全失效。原本的策略是基于内存压力阈值做决策但在低显存环境下压力长期处于高位阈值判断失去了区分度导致调度器要么过度保守什么都不加载要么过度激进频繁 OOM。排查过程把调度器的决策日志打出来发现大部分决策都落在压力高这个区间但实际的内存余量差异很大。阈值策略在压力分布集中的场景下不适用。根因单一阈值无法应对压力分布集中的情况。需要引入更细粒度的指标比如内存压力的变化率、模型加载的边际成本等。修复方案把调度策略从阈值判断改成成本收益评估。每个模型加载请求计算一个收益值基于请求优先级和预期运行时长和一个成本值基于预估内存占用和当前压力调度器选择收益成本比最高的请求执行。这个改动让低显存环境下的调度变得合理很多。3.6 第六个坑MoE 架构模型的内存行为与稠密模型不同我加载了一个 MoE 架构的模型发现它的内存占用模式跟稠密模型完全不一样。稠密模型加载后内存占用基本稳定MoE 模型在推理时内存占用会波动因为不同专家被激活时会有额外的加载和卸载。排查过程监控了 MoE 模型推理过程中的内存曲线发现波动幅度能达到 1G 以上。管理器的预算没有考虑这个波动导致在波动峰值时触发 OOM。根因MoE 架构的专家并行机制导致内存占用是动态的。如果所有专家都常驻内存占用会很大如果按需加载就会有波动。修复方案对 MoE 模型单独设置预算按所有专家常驻的最坏情况来算或者配置专家缓存策略限制同时驻留的专家数量。我选择了后者配合管理器的内存监控在波动峰值前提前做预防性卸载。3.7 第七个坑长时间运行后的内存碎片化这个坑最隐蔽跑几个小时甚至十几个小时后才出现。现象是明明总内存还有余量但加载新模型时就是分配失败。重启管理器后恢复正常。排查过程用内存分析工具看了长时间运行后的内存布局发现大量小的空闲块散布在已分配区域之间没有足够大的连续空间给新模型。根因频繁的加载卸载导致内存碎片化。统一内存虽然是一整块物理内存但分配器管理下会产生碎片。修复方案两个措施。一是管理器定期做一次内存整理在低负载时段卸载所有模型再按需重新加载相当于重置内存布局。二是尽量让模型常驻减少加载卸载频率。这两个措施配合碎片化问题基本可控。4. 调度策略的迭代从阈值到成本收益模型4.1 第一版简单阈值策略的问题第一版调度器就是简单的阈值判断内存压力低于 70% 就允许加载高于 85% 就触发卸载。这个策略在负载平稳时能用但一旦负载波动就出问题。最典型的是抖动——加载一个模型把压力推到 86%触发卸载卸载后压力降到 68%又允许加载如此循环。这个问题的本质是阈值策略没有考虑决策的滞后效应。加载和卸载都不是瞬时完成的在操作进行中压力会继续变化基于瞬时值的判断很容易做出错误决策。4.2 第二版引入滞后区间和预测第二版加了两个东西。一是滞后区间加载阈值和卸载阈值之间留出足够的间隔比如加载阈值 70%卸载阈值 90%避免在边界反复横跳。二是简单的趋势预测根据最近几次采样的内存变化率预测未来几秒的压力走向提前做决策。这一版明显稳定了但在多模型竞争的场景下还是不够。当多个请求同时到达时调度器缺乏一个统一的优先级框架来决定先满足谁。4.3 第三版成本收益评估框架第三版是我目前用的版本。核心思路是给每个加载请求算一个分数收益分基于请求的优先级、预期运行时长、以及用户显式指定的权重成本分基于模型预估内存占用、当前压力、以及加载所需时间决策按收益成本比排序依次满足直到内存预算用尽这个框架的好处是可扩展。比如你可以给交互式请求更高的优先级给批处理任务更低的优先级可以给短任务更高的收益分因为它的内存占用时间短。实际跑下来多模型竞争场景下的体验比阈值策略好很多。4.4 调度器的监控与调优调度器本身也需要监控。我记录了几个关键指标决策延迟、加载成功率、OOM 次数、模型平均驻留时长。这些指标能帮你判断调度策略是否合理。调优的时候有个经验不要追求极致的资源利用率。留一点余量让系统有应对突发的能力比把内存榨干要好。我现在的目标是内存利用率维持在 75% 到 85% 之间这个区间下系统既不会浪费太多又有足够的弹性。5. 统一内存场景下的实操配置清单5.1 系统层面的配置系统层面有几项配置对统一内存场景影响很大。首先是内存分配策略。Linux 下可以通过调整vm.overcommit_memory和vm.swappiness来影响内存分配行为。我的配置是overcommit_memory1允许过度分配和swappiness10尽量不使用交换。前者让大块分配更容易成功后者避免模型被换出到磁盘导致性能骤降。其次是大页配置。统一内存下使用大页能减少 TLB miss对推理性能有正面影响。具体配置取决于你的系统支持一般是在内核参数里开启透明大页或者显式预留大页。第三是GPU 侧的内存分配器配置。ROCm 环境下有一些环境变量可以调整分配器的行为比如缓存大小、分配粒度等。这些参数需要根据你的负载模式调没有通用最优值。5.2 模型格式与量化选择GGUF 是目前本地部署最常用的格式它的量化选项很丰富。我的经验是量化级别文件大小7B质量损失适用场景Q8_0约 7.5G极小对质量要求高内存充足Q6_K约 5.8G很小平衡选择Q5_K_M约 5.0G小推荐默认Q4_K_M约 4.2G可接受内存紧张Q3_K_M约 3.5G明显极限压缩在统一内存场景下我倾向于选 Q5_K_M 或 Q4_K_M因为要同时驻留多个模型单个模型的占用不能太大。质量损失在大多数任务上可以接受。5.3 管理器与推理框架的对接要点对接推理框架时有几个点容易出问题。一是加载接口的线程安全性。有些框架的加载接口不是线程安全的并发调用会出问题。管理器需要做串行化或者用锁保护。二是卸载接口的幂等性。卸载一个已经卸载的模型有的框架会报错有的会静默成功。管理器需要处理这两种情况。三是错误传播。加载失败时框架可能抛出异常也可能返回错误码。管理器需要统一处理并且把错误信息透传给上层。四是资源释放的时机。有些框架的释放是异步的调用释放接口后资源不会立即回收。管理器需要等待确认或者定期检查。6. 性能实测五个模型共存时的真实表现6.1 测试环境与模型组合测试环境是一台 128G 统一内存的设备ROCm 环境。五个模型分别是一个 7B 对话模型Q5_K_M、一个 3B 代码模型Q5_K_M、一个图像生成模型、一个语音转写模型、一个小的嵌入模型。这个组合覆盖了文本、图像、语音三类任务比较有代表性。总内存占用在满载时约 95G留了 30G 左右的余量给系统和突发。6.2 单模型性能基线先测了每个模型单独运行时的性能作为基线。对话模型在 32K 上下文下的生成速度约 25 token/s代码模型约 40 token/s图像模型生成一张 1024x1024 的图约 15 秒语音转写实时率约 0.3即 10 秒音频约 3 秒转完。这些数字跟独显环境比有差距但在统一内存设备上属于正常水平。差距主要来自内存带宽统一内存的带宽通常低于高端独显。6.3 五模型共存时的性能衰减五个模型同时驻留但只有一个在运行时性能衰减很小基本在 5% 以内。这说明统一内存的容量优势确实能体现出来模型常驻避免了加载开销。但当两个或更多模型同时高负载运行时衰减就明显了。两个模型同时推理时各自的吞吐会降到单模型时的 60% 到 70%。三个以上同时高负载衰减更严重而且会出现请求排队。这个现象的本质是内存带宽竞争。统一内存的带宽是共享的多个模型同时读写会互相挤占。所以五个模型共存和五个模型同时跑是两个概念前者可行后者要谨慎。6.4 调度策略对体验的影响对比了阈值策略和成本收益策略下的用户体验。在混合负载场景下成本收益策略的请求平均等待时间比阈值策略低约 30%OOM 次数从每小时几次降到几乎为零。这个提升主要来自两个方面一是成本收益策略能更合理地安排加载顺序避免高优先级请求被低优先级请求阻塞二是它能更准确地预估内存需求减少因预算错误导致的失败。7. 踩坑之后沉淀下来的几条经验7.1 内存预算宁可保守我最初做预算时倾向于乐观估计觉得留太多余量是浪费。结果就是频繁 OOM排查成本远高于那点浪费。后来改成保守估计留 15% 到 20% 的余量系统稳定性大幅提升。统一内存场景下内存压力的变化比独显环境更复杂因为系统层面的活动也会影响可用内存。保守一点给突发留空间是更明智的选择。7.2 模型常驻优于频繁切换加载卸载模型的开销比想象中大。除了加载本身的时间还有内存碎片化、缓存失效、计算图重编译等隐性成本。如果内存允许让常用模型常驻比按需加载卸载的体验好很多。我的做法是把模型分成热和冷两类。热模型常驻冷模型按需加载。热模型的判断基于最近的使用频率管理器会自动调整。7.3 监控比调优更重要我花了很多时间在调优调度参数上后来发现建立完善的监控体系更有价值。有了监控你能看到系统在什么情况下出问题能定位到具体的瓶颈调优才有方向。监控的关键指标包括内存占用的时间序列、模型加载卸载的频率、请求的等待时间分布、OOM 的发生时机。这些数据能帮你做出更准确的决策。7.4 不同框架的行为差异要摸清每个推理框架的内存管理行为都不一样。有的加载后立即占用全部内存有的按需增长有的卸载彻底有的留缓存。管理器要适配这些差异不能假设所有框架行为一致。我的做法是给每个框架写一个适配层把框架特有的行为封装起来管理器只跟统一的接口打交道。这样增加新框架时只需要写适配层不用改管理器核心。7.5 碎片化是长期运行的隐形杀手短时间测试发现不了碎片化问题跑几个小时甚至十几个小时才会暴露。如果你的场景需要长时间运行一定要考虑碎片化。应对碎片化的手段有限最有效的是定期重启或做内存整理。我把内存整理安排在低负载时段自动执行对用户几乎无感。8. 后续可以继续深挖的方向管理器目前能稳定跑五个模型但还有优化空间。一个方向是更精细的内存预测用机器学习模型来预测不同请求的内存需求比现在的静态估算更准。另一个方向是跨设备调度如果有多台设备可以把模型分布到不同设备上管理器做全局调度。还有一个方向是跟 ComfyUI 这类工作流工具做更深度的集成。现在的预留接口比较粗糙如果能做到工作流级别的资源规划体验会更好。比如一个工作流需要哪些模型、按什么顺序加载、峰值内存是多少这些信息如果能提前获取调度器就能做更优的决策。这些方向我还在探索有进展会继续分享。统一内存设备跑多模型的场景会越来越常见希望这些踩坑经验能帮到同样在折腾的人。