城东的快递分拣中心,主管老陈管着三个车间——一号、二号、三号,每个车间都有自己的货架。过去各车间各管各的:包裹要从一号车间送到二号车间,得先卸到中央转运场再装车拉过去;货架上零散的空位看着不少,可大包裹来了怎么也塞不下;还有些包裹被塞进犄角旮旯,再没人想起,默默占着货架。老陈头疼了很久,直到 IT 团队装上一套“智能仓储系统”:所有货架统一编号,包裹贴上电子标签,车间之间加装直通滑道,无人巡检车每天自动盘点、按需清仓。AI 世界里,异构内存管理做的就是同样的事——把 GPU、CPU、NPU 看成一个个车间,把显存、主存看成各车间的货架,用一套统一的“仓储系统”把内存的分配、调度、回收管起来,让模型推理又快又稳地一直跑下去。
掌握异构内存管理,是 AI Runtime 工程师驾驭多卡推理服务的基本功——它直接决定系统能否在数周甚至数月的长时运行中保持高吞吐、零崩溃、不泄漏。
目录
可视化演示
动态演示异构内存管理的核心机制,通过全局/局部/动态/静态四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
异构内存管理:异构内存 异构内存架构与统一内存池设计 零拷贝跨设备访问机制 核心机制可视化 异构内存管理整体架构与关键组件
一、异构内存挑战:三个车间各管各的
老陈最初的问题,是三个车间“各扫门前雪”。一号车间空着两百个格子,二号车间挤得要命,可调度员宁可让包裹在二号车间排队,也不敢把货挪到一号车间——因为跨车间找货要从头翻一遍台账,搬过去容易、查起来难。更气人的是,货架上的空位散得像筛子:左边空一格、右边空两格,看着地方挺大,可一个大箱子放下去,哪个缝隙都装不下。这就是“看得见的总量,看不见的碎片”。
换成 AI 场景,三个车间就是三块异构设备——GPU、CPU、NPU。它们各自拥有独立的地址空间,谁也没法直接读对方的显存;跨设备访问要先拷贝到主机内存,再拷贝到目标设备。显存碎片化、内存泄漏、传输开销,这些“仓库病”在推理服务里一个不少:多设备隔离让指针无法跨设备使用,显存碎片化让大张量的连续分配频繁失败,内存泄漏来自忘记释放的张量句柄,传输开销在高频小张量场景尤为致命。
衡量碎片化严重程度,最常用的指标是“碎片率”:
老陈后来明白,光看“还剩多少格子”远远不够。要把仓库盘活,得先换掉“各管各”的老规矩——于是 IT 团队搬出了那张让人眼前一亮的总台账。
- 最大连续空闲块在所有空闲格子中找出一条最长的连续空位,它决定了能否容纳一个大包裹。
- 总空闲容量把各处零散的空闲小格子全部加起来,总数看着不少但大箱子无处安放。
- 计算碎片率1 减去两者比值,结果越接近 1,碎片化越严重,越容易分配失败。
二、统一内存池:一张总台账
IT 团队带老陈看样板间:每个车间门口挂一块大屏,屏幕上只有一张总台账,所有货架格子统一编号,从 0001 到 9999。不管包裹物理上放在哪个车间,账上都只登记一个编号;调度员只要报出编号,系统就能指出它躺在哪排货架。老陈一拍大腿:这就是他要的“大局观”。
在技术世界里,这张总台账就是统一内存池。Runtime 在底层为每个设备维护虚拟地址空间,通过统一虚拟寻址把所有设备内存编进同一张虚拟地址地图。上层程序拿到一个指针就能跨设备访问,不再关心物理位置;每个设备的显存、主存都成为池中的一部分,由统一的分配器管理。
池子内部还要讲究分配策略:首次适配(first fit)找第一块够大的空位,速度快;最佳适配(best fit)挑最贴合的空位,省空间但查找慢;伙伴系统(buddy system)把内存按 2 的幂切成块,分裂与合并都方便,是分配器常用的结构。频繁申请同一大小的块时,缓存复用让分配请求不再下探到驱动层,显著降低延迟。不过伙伴系统按整块给出内存,也会带来内碎:
总台账让仓库有了大局观,但老陈很快发现新瓶颈:包裹在不同车间之间搬运时,还在走“卸到中央转运场再装车”的老路——跨设备传输的成本,是时候被砍一刀了。
- 请求大小任务实际申请的空间大小,例如“我要 5 个格子放包裹”。
- 块大小货架按 2 的幂打包(如 4、8、16 格),分配时只能按整包给出。
- 计算内碎率1 减去两者比值得出浪费比例,块越大、请求越小,浪费越严重。
三、跨设备零拷贝:直通滑道
老陈算过一笔账:一次跨车间调拨,包裹要先从一号车间卸下来,推到中央转运场,再由三轮车拉到二号车间,再搬上货架——光装卸就折腾了两回。IT 团队给出方案:在两个车间之间加一条直通滑道,传送带直接对接,包裹从一号车间滑到二号车间,中途不再落地。老陈给它起了个名字:“零落地搬运”。
对应到内存世界,普通跨设备访问就是“卸到主机再装车”:GPU0 的显存先复制到主机内存,再由主机内存复制进 GPU1,两次内存复制都要 CPU 亲自参与。而零拷贝机制(zero-copy)让 DMA 引擎和互连总线直接搬运,CPU 只发指令、不搬数据。具体手段包括:P2P 直连(GPU 之间通过 NVLink/PCIe 直接通信)、共享虚拟内存(两设备映射同一段物理内存)、统一寻址(设备内核直接用同一个指针读写)。两种路径的耗时对比如下:
零拷贝不是魔法——数据仍然要动,只是省掉了两次“装卸”,让 CPU 从搬运工变成了指挥官。可老陈又想到一个问题:滑道再快,也得知道包裹该不该搬、用完没有。这就轮到“出库单”登场了。
- 普通拷贝耗时数据先复制到主机、再从主机复制到目标设备,出现两段内存复制。
- 链路传输时间数据量除以链路带宽,这是跨设备搬运无法避免的物理时间。
- 零拷贝耗时DMA 引擎直接完成搬运,省掉两段内存复制,只剩引擎开销加链路时间。
四、生命周期管理:自动清仓的出库单
老陈最头疼的是“失踪包裹”:拣货员随手把包裹塞进角落,出库单上却没人记录,等想起来时,包裹已经在货架上躺了三个月。IT 团队给每个包裹贴一张动态出库单——谁要用,就在单子上加一笔;谁用完,就划掉一笔。单子归零,系统自动通知巡检机器人把包裹送去回收区。从此,失踪包裹绝迹了。
这张出库单就是引用计数(reference counting)。Runtime 为每个内存块维护一个引用计数:任务申请或共享时加一,释放引用时减一,归零即回收。配合智能指针,加减一的操作完全自动化,程序员不再需要亲手管理释放时机。推理服务中,每个算子的输出张量、每个队列里的中间结果,都靠这套机制在恰当的时刻被放回内存池。
引用计数也不是万能钥匙:两个内存块互相引用时,计数永远不会归零,就会“抱团泄漏”。因此成熟的方案会叠加垃圾回收(GC)兜底,定期扫描并回收循环引用;再配合泄漏检测,在分配站点记录调用栈,定位长时运行中缓慢增长的内存。引用计数判据如下:
老陈总结出心得:让每件货物都有人记账,仓库才不会越用越堵。但光是“不堵”还不够——各车间忙闲不均的问题,需要一个聪明的调度员来解决。
- 引用计数累加每个使用方挂一张出库单,引用一次就加一笔,r 代表每个任务的引用数。
- 引用解除减一任务用完划掉一笔,总计数随之减少,记录当前真实使用人数。
- 归零判定回收当计数为 0 说明无人使用,系统将该内存块放回池子。
五、内存调度策略:聪明的调度员
调度员小周很快发现,就算有了总台账和滑道,各车间的忙闲还是常常失衡:一号车间堆得像山,二号车间空得能跑马。小周的秘诀是“看颜色”:每个包裹贴一张温度标签,三天没动过的标成蓝色(冷),正在被频繁捡起的标成红色(热)。冷包裹优先挪去空车间,热包裹留在老地方;货架快满时提前拉响警铃。这一套“看颜色”的土办法,正是内存调度的智慧。
落到工程里,这套策略包含四个动作:亲和性分配尽量把内存放在数据原本所在的设备,减少迁移;冷热迁移定期扫描访问频率,把冷块挪走或换出,给热数据腾地方;容量水位为每个池子设定高水位、低水位,超过高水位触发迁移或淘汰,低于低水位归还闲置内存;再辅以优先级调度,让延迟敏感的关键任务优先获得内存,宁可牺牲后台任务的分配机会。
水位触发机制的数学模型很直观:
小周的“看颜色”让每个车间的货架始终松紧有度。老陈很满意,但他心里还有最后一块石头:这套系统真能一年到头不出岔子吗?
- 内存利用率已占用空间除以总容量,衡量货架的满空程度。
- 高水位迁移当利用率过高触发冷块迁移,避免货架爆满导致分配失败。
- 低水位回收利用率过低时回收多余空闲货架,把资源还给其他服务。
六、工程实践落地:长稳运转的保障
半年后,分拣中心再没出过大乱子。但 IT 团队依然每天盯着监控大屏:货架使用率、空位分布、滑道吞吐、异常告警。老陈自己也养成习惯,每天早会先看三样数据——总空闲、碎片率、未处理包裹数。他说:“稳定不是撞大运,是盯出来的。”
在 AI 工程里,这套系统正支撑着最关键的场景。大模型推理服务的 KV Cache 是显存消耗大户:把缓存块放入统一内存池、按需分配、用完自动回收,避免请求高峰期的剧烈抖动;性能监控持续上报碎片率、命中率和传输带宽;水位预警在显存告急时先把冷块迁移,而不是等到 OOM 再重启进程。多实例共享、动态扩容、失败回退,这些工程细节共同构成“长稳运行”的底座。
评判池化复用效果,最核心的指标是缓存命中率:
从“各管各”到“一张总台账”,再到直通滑道、出库单、温度标签、监控大屏——异构内存管理最终是一门“让系统长久稳定”的手艺。老陈的分拣中心还在运转,而 AI 推理服务有了这套系统,也能在成千上万次请求中稳稳地跑下去。
- 命中与未命中次数从缓存池直接拿到内存的次数记为 H,需要重新分配的次数记为 M。
- 命中率计算命中次数占总请求的比例,越高说明复用做得越好。
- 平均耗时加权命中走快速通道、未命中走慢速通道,按比例加权得到平均耗时。
AI 应用场景
大模型推理服务的 KV Cache 内存池
阶段:推理部署与在线服务
解决的问题:传统方案每次请求单独申请、释放显存,高并发时碎片化加剧、分配抖动频繁,连续运行几天后显存逐渐被泄漏耗尽。统一内存池把 KV Cache 块化预分配,配合引用计数自动回收与零拷贝跨卡访问,让多路并发推理保持稳定低延迟。
具体案例:某 7B 参数模型部署在 2 张 A100 上,32 路并发推理,KV Cache 峰值 40GB;采用块池管理后碎片率从 18% 降到 3%,连续运行 7 天无 OOM。
多卡并行训练中的梯度 AllReduce
阶段:训练优化
解决的问题:数据并行训练每一步都要做梯度 AllReduce,梯度张量跨卡频繁通信,普通拷贝经主机中转让 CPU 成为通信瓶颈。用 P2P 直连加零拷贝调度,跨卡通信绕过主机,梯度聚合直接走设备互连,通信时间显著缩短。
具体案例:8 卡 A100 训练千亿参数模型,梯度同步耗时占比从 34% 降到 19%,端到端训练吞吐提升约 12%。
Python 演示
class HeteroMemory: def __init__(self, devices, size): self.pools = {d: [0] * size for d in devices} self.size = size self.refs = {} self.next_id = 0 def alloc(self, device, size): blocks = self.pools[device] cnt = 0 start = -1 for i, b in enumerate(blocks): cnt = cnt + 1 if b == 0 else 0 if cnt == size: start = i - size + 1 break if start < 0: return None for i in range(start, start + size): blocks[i] = 1 buf_id = self.next_id self.next_id += 1 self.refs[buf_id] = {"device": device, "start": start, "size": size, "ref": 1} return buf_id def attach(self, buf_id): if buf_id in self.refs: self.refs[buf_id]["ref"] += 1 def detach(self, buf_id): if buf_id not in self.refs: return self.refs[buf_id]["ref"] -= 1 if self.refs[buf_id]["ref"] == 0: self._free(buf_id) def move(self, buf_id, target): info = self.refs.get(buf_id) if info is None or info["device"] == target: return buf_id info["device"] = target # 零拷贝共享: 只改归属不改物理位置 return buf_id def _free(self, buf_id): info = self.refs.pop(buf_id) for i in range(info["start"], info["start"] + info["size"]): self.pools[info["device"]][i] = 0 def usage(self): return {d: sum(self.pools[d]) for d in self.pools} if __name__ == "__main__": mem = HeteroMemory(["GPU0", "GPU1"], 16) a = mem.alloc("GPU0", 4) mem.attach(a) b = mem.alloc("GPU1", 3) print("分配后用量:", mem.usage()) mem.detach(a) mem.detach(a) print("引用归零后:", mem.usage()) mem.move(b, "GPU0") print("零拷贝共享后:", mem.usage())
self.refs[buf_id] = {"device": device, "start": start, "size": size, "ref": 1}每分配一块内存就登记一张“出库单”,记录设备、位置和引用数cnt = cnt + 1 if b == 0 else 0用连续空闲计数寻找第一块足够大的空间,模拟首次适配if self.refs[buf_id]["ref"] == 0: self._free(buf_id)引用归零立即自动回收,这是生命周期管理的核心判定info["device"] = target只改归属不改物理位置,正是共享虚拟内存下的零拷贝思想self.pools[info["device"]][i] = 0把格子归还货架,内存池重新获得空闲块
常见误区
误区零拷贝是魔法,直接让 A 卡显存里的数据“凭空出现”在 B 卡,一点成本都不用花。
正解数据物理上仍然要从 A 设备流动到 B 设备,零拷贝省去的是 CPU 参与的两次内存复制和中间临时缓冲,由 DMA 引擎和互连总线转发。就像直通滑道取代了“卸到转运场再装车”——包裹还在动,只是省掉了两次装卸。
为何常见:“零”字太有迷惑性,让人以为完全免费;其实免费的是 CPU 的搬运时间,不是物理传输本身。
误区既然池子能减少碎片和分配失败,那就把每个设备的池子开到最大,一劳永逸。
正解池子太大会挤占其他服务的内存,分配查找与水位迁移的开销也随之上升;正确的做法是按峰值负载设定容量,配合低水位回收,保持各设备水位均衡,而不是一味贪大。
为何常见:直觉上“空间多等于安全”,忽略了内存是共享稀缺资源,池子大小需要全局权衡。
误区只要每个内存块都记录了引用次数、归零就释放,泄漏问题就彻底解决了。
正解引用计数无法识别循环引用——A 引用 B、B 引用 A,两个计数永远不为零,内存照样泄漏。需要垃圾回收检测循环引用,并用泄漏检测定位长时运行中缓慢增长的内存。
为何常见:引用计数模型简单直观,容易让人高估它的完备性,忽略了“互相抱团”的极端场景。
误区碎片化只出现在显存小的设备上,显存大就不会有“放不下”的问题。
正解碎片化取决于分配与释放的模式而非总容量。只要请求大小参差不齐、释放顺序随意,大显存同样会被切得支离破碎,大请求照样分配失败。
为何常见:把碎片化误当成容量不足,忽略了它与分配序列的关系——货架再大,空位散成筛子也放不下大箱子。
面试题
Q1. 多设备异构内存管理主要解决哪类问题?
- A.压缩模型的参数体积
- B.多设备内存碎片与泄漏← 正确答案
- C.不断提升单卡显存规格
- D.加快底层硬件的时钟
答案:B。异构内存管理的核心目标是统一管理多设备内存,解决碎片化、泄漏和跨设备传输问题。“碎”与“漏”是长时运行的元凶。压缩参数属于模型优化,提升显存规格属于硬件演进,加快时钟属于硬件提速,均不属于内存管理的职责。
Q2. “零拷贝”跨设备访问主要省去了什么开销?
- A.内存池中的块对齐步骤
- B.模型权重的加载过程
- C.CPU 参与的两次数据搬运← 正确答案
- D.设备间链路传输的延迟
答案:C。普通跨设备拷贝走“设备→主机→设备”路径,CPU 参与两次内存复制;零拷贝让 DMA 引擎直接完成搬运,CPU 只发指令。链路延迟是物理传输必然存在的,块对齐与权重加载和零拷贝无关。
Q3. 某设备空闲总量 100MB,最大连续空闲块只有 20MB,其碎片率约为?
- A.0.80← 正确答案
- B.0.20
- C.0.50
- D.0.10
答案:A。碎片率 = 1 - 最大连续空闲块 / 总空闲容量 = 1 - 20/100 = 0.80。碎片率越接近 1,说明空间越碎,大请求越难分配成功,这与“空位总数很多”形成鲜明对比。
Q4. 某内存块引用计数归零后,系统应执行哪一步?
- A.立即触发一次全量垃圾回收
- B.立即把该块迁移到另一设备
- C.立即把该块锁定为常驻内存
- D.立即把该块标记为可回收← 正确答案
答案:D。引用归零表示无人在用,应将该块标记为可回收并归还内存池。全量垃圾回收是代价较大的兜底动作,不必每次触发;迁移与锁定都与“回收”语义相反。归零即回收,是生命周期管理的核心判定。
Q5. 下列哪些属于内存调度策略?
- A.亲和性分配← 正确答案
- B.冷热迁移← 正确答案
- C.容量水位调节← 正确答案
- D.模型量化压缩
答案:ABC。亲和性分配、冷热迁移、容量水位调节是典型的内存调度手段,分别解决数据就近、冷块腾挪和容量均衡问题。模型量化压缩属于模型优化技术,不归内存调度管理。
Q6. 请求 5MB 内存而伙伴系统给出 8MB 块,额外浪费的 3MB 属于?
- A.外部碎片
- B.内存泄漏
- C.内部碎片← 正确答案
- D.缓存未命中
答案:C。伙伴系统按 2 的幂给出整块,请求 5MB 给 8MB,多余的 3MB 在块内部无法使用,叫内部碎片。外部碎片是块与块之间的零散空位;泄漏是忘记释放;未命中是缓存概念,都与本题无关。
Q7. 下列哪些机制可以降低跨设备数据传输开销?
- A.零拷贝直连设备间内存← 正确答案
- B.共享虚拟内存统一寻址← 正确答案
- C.P2P 直连绕过主机中转← 正确答案
- D.增加指令级并行度
答案:ABC。零拷贝、共享虚拟内存、P2P 直连都让数据绕开主机中转,是降低跨设备开销的标准手段。指令级并行属于 CPU 内部的流水线优化,与跨设备数据传输没有直接关系。
Q8. 长时运行中显存泄漏的典型表现是?
- A.空闲内存持续增加
- B.可用内存逐渐减少← 正确答案
- C.传输带宽越来越大
- D.模型推理速度变快
答案:B。泄漏的本质是“只借不还”,可用显存随运行时间逐渐减少,最终 OOM 崩溃。空闲内存增加、带宽变大、速度变快都不是泄漏特征;识别这类缓慢下降趋势正是泄漏检测的职责。
Q9. 推理服务部署中应重点监控哪些指标?
- A.内存碎片率与使用水位← 正确答案
- B.内存池缓存命中率← 正确答案
- C.跨设备传输带宽← 正确答案
- D.训练集样本多样性
答案:ABC。碎片率、水位、命中率、传输带宽都是内存健康度的核心指标,直接反映长稳运行状态。训练集样本多样性属于数据层面,与运行时内存监控无关。
Q10. 统一内存池的核心价值在于?
- A.单卡显存容量无限扩大
- B.程序按统一接口使用内存← 正确答案
- C.模型推理必定零延迟
- D.训练数据不再需要清洗
答案:B。统一内存池通过统一虚拟寻址屏蔽设备差异,让程序用统一接口使用所有设备的内存,这是多设备协作的基础。它不会让显存无限扩大,也不能保证零延迟,更与数据清洗无关。