如果把AI系统比作一座大型物流园,CPU仓库、GPU仓库和各类加速器仓库各占一方。模型推理时,一份“张量货箱”可能先在CPU仓库完成预处理,又必须运到GPU仓库做矩阵计算,最后再把结果送回CPU仓库交给用户。掌管这一切的调度员,就像物流调度台的老张,最关心的是:货怎么搬、走哪条路、什么时候搬,才能让整个流水线不停工。 数据迁移,就是这些跨设备“货箱搬运”的总称。它不像核爆般的计算那样光鲜,却很实在:如果搬运规划得差,推理服务会卡在等待拷贝上,吞吐量骤降;如果规划得好,计算与通信可以像流水线一样同时开动,让GPU几乎一刻不停。本篇我们将跟着老张,从物流视角理解数据迁移的成本、异步通信、调度策略,以及在推理部署中的落地。

岗位关联

对AI从业者而言,掌握数据迁移等于掌握推理性能的“隐形杠杆”。在跨设备部署越来越普遍的今天,能通过调度与重叠技术把通信成本藏起来,是把理论跑成吞吐量的核心能力。

目录
  1. 可视化演示
  2. 迁移的根源
  3. 张量搬运基本功
  4. 开销从哪来
  5. 计算通信重叠
  6. 调度器决策
  7. 性能衡量工具
  8. 推理落地实践
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

迁移的根源

老张管理的物流园不断扩张,CPU仓库擅长快速处理杂活儿,GPU仓库擅长并行重活,NPU仓库则专门处理特定算子。一张计算图画出来后,不同步骤落在不同仓库。货箱在CPU仓库加工完,接下来只能在GPU仓库继续,这时候就必须跨仓搬运。

AI Runtime系统里的“货箱”就是张量。每个张量都活在设备自己的内存里:CPU内存与GPU显存彼此隔离,地址空间不互通。GPU不能直接读CPU上的数组,CPU也不能直接访问显存指针。计算图只要跨设备,调度器就必须插入一次数据迁移。

迁移不是免费的。搬运时间可以写成一个非常朴素的公式:

老张很清楚:如果每箱货都要等卡车到了再开工,整个园区就会僵住。所以,了解搬运成本只是第一步,更关键的课题是——如何让搬运不再让人等车。

T传输=DB\displaystyle T_{\text{传输}} = \frac{D}{B}
就好比货车运送一箱货物,耗时等于货物量除以卡车运力。
  1. 分子 \(D\)这是要搬运的数据量,单位是字节。张量越大、批越大,D就越大。
    D\displaystyle D
  2. 分母 \(B\)这是有效带宽,单位是字节每秒。它代表CPU与GPU之间的实际搬运速度,受总线、驱动和内存类型影响。
    B\displaystyle B
  3. 整体 \(T_{\text{传输}}\)数据量除以带宽得到搬运秒数。调度器看到这个数字,就能判断迁移是否值得。
    T传输\displaystyle T_{\text{传输}}

张量搬运基本功

物流园里的货位不是千篇一律的。老张的园子里有几类仓库:普通货架可以随意堆放,但货车装货时还得先人工翻找;锁页货架则像固定泊位,货车可以直接靠上去装货;GPU仓库里的货架则是高速通道,进出非常快。

内存类型也是这个道理。“普通内存”叫可分页内存,操作系统可能把页面换到磁盘,DMA设备不能直接碰,于是拷贝前必须先复制到一块锁页内存,多走一步路。“锁页内存”也叫固定内存,地址稳定,支持设备直接访问,是从CPU搬运到GPU的重要中转站。GPU显存是设备侧的高速存储,而“GPU与GPU之间”则可能走NVLink等高速互联,比CPU到GPU更快。

想要估算一次迁移的数据体积,可以用张量占用的字节数:

老张明白了:同样的箱子,选错货位会让装卸多绕几道弯。于是他在规划迁移前,会先确认货从哪里出、路上走哪条链路,才不至于白费力气。

bytes=B×C×H×W×d\displaystyle \text{bytes} = B \times C \times H \times W \times d
一个视觉模型的特征图有多大,要看批量大小、通道数、高宽和每个元素的字节数,四者相乘再乘d。
  1. 批量大小 \(B\)一次送入多少个样本。批量越大,货箱越大。
    B\displaystyle B
  2. 通道数 \(C\)特征图的通道数量,通常对应卷积核的数量。
    C\displaystyle C
  3. 高与宽 \(H, W\)特征图的空间尺寸,高宽越大,搬运重量越高。
    H×W\displaystyle H \times W
  4. 元素字节数 \(d\)每个数值占几个字节,例如FP32占4字节。
    d\displaystyle d

开销从哪来

单看公式,搬运似乎只有“重量除以速度”。可老张经验丰富,他知道还有绑绳、开门、签收这些固定动作。即使货物只有一小箱,卡车启动也要花时间。

实际拷贝开销由两部分组成:

更可怕的是同步等待:调度器如果让计算单元等待拷贝结束再干活,那么这段迁移时间就成了纯空闲。老张看到这一幕直摇头:“卡车装卸时工人不干活,这园区效率太低了。”于是,真正的优化思路浮出水面:能不能让搬运和加工同时进行?

Tcopy=DBeff+Toverhead\displaystyle T_{\text{copy}} = \frac{D}{B_{\text{eff}}} + T_{\text{overhead}}
实际耗时不仅包括数据量除以有效带宽,还要叠加一次固定的启动开销。
  1. \(D / B_{\text{eff}}\)把真实数据量除以有效带宽。有效带宽可能达不到理论峰值,所以这里用的是实际观察值。
    DBeff\displaystyle \frac{D}{B_{\text{eff}}}
  2. \(T_{\text{overhead}}\)调一次拷贝接口、建立传输通道、同步收尾的固定开销。即使搬1字节也要付这笔“起步价”。
    Toverhead\displaystyle T_{\text{overhead}}
  3. 总和 \(T_{\text{copy}}\)实际一次数据迁移至少花这么多时间。如果中途还会经过中间缓冲,开销会更高。
    Tcopy\displaystyle T_{\text{copy}}

计算通信重叠

老张的物流园引进了连续流水线:这边货车还在卸货,那边的加工台已经开始处理上一批货。虽然单次卸货时间没变,但整个园区不再干等。

在AI Runtime里,这个思想叫“计算通信重叠”。它利用异步拷贝接口,把数据搬运提交给独立的拷贝引擎,CPU和GPU上的计算不用阻塞;再用双缓冲,让当前张量参与计算的同时,下一个张量已经提前搬到设备上。如果通信时间短于计算时间,通信可以被完全藏进计算的“影子”里。

对于一次搬运和一段计算,重叠后的关键路径是:

老张乐了:以前“先卸货再做工”要等整车,现在只要加工时间比卸货长,整车搬运就像不存在一样。这正是异步流水线、多流并发能提升吞吐的底层原因。

Toverlap=max⁡(Tcomm,Tcalc)\displaystyle T_{\text{overlap}} = \max\left( T_{\text{comm}}, T_{\text{calc}} \right)
搬运和计算并行执行,总耗时只取决于两者中更长的那一个,而不是相加。
  1. \(T_{\text{comm}}\)数据迁移需要的时间。
    Tcomm\displaystyle T_{\text{comm}}
  2. \(T_{\text{calc}}\)计算算子需要的时间。
    Tcalc\displaystyle T_{\text{calc}}
  3. \(\max\) 取较大值因为二者在同一时间窗口并行,谁慢谁决定最终耗时。
    max⁡(Tcomm,Tcalc)\displaystyle \max\left( T_{\text{comm}}, T_{\text{calc}} \right)

调度器决策

老张不会满足于只优化一条路线。他会看全局:哪个仓库的活儿可以挪到一起?哪个货箱其实不需要出仓,直接在本地加工?要不要多租几辆货车同时跑?

调度器决策的本质,是一个规划问题。它要决定每个算子放在哪个设备上,决定执行顺序,还会尝试消除不必要的数据拷贝。比如某个中间张量只被下一个算子使用,那调度器可以让算子就地融合,省掉一次搬运。再比如内存池复用:已经分配的显存空间可以反复装新货,避免反复申请和释放。

调度器判断一个新方案是否值得,可以看收益:

老张在调度台上做决策时,从不在乎“看着忙不忙”,只在乎“货有没有及时到位”。所以在真实系统中,调度器也会根据收益函数,在所有可迁移点之间寻找最佳组合。

Gain=Told−(Tcopy+Tnew)\displaystyle \text{Gain} = T_{\text{old}} - \left( T_{\text{copy}} + T_{\text{new}} \right)
如果优化后的总耗时比原来少,说明这次调整值得做。
  1. \(T_{\text{old}}\)当前方案的计算与执行总耗时。
    Told\displaystyle T_{\text{old}}
  2. \(T_{\text{copy}}\)新方案带来的数据迁移开销,可能是新增的,也可能是消除后剩下的。
    Tcopy\displaystyle T_{\text{copy}}
  3. \(T_{\text{new}}\)新方案中计算本身的耗时。
    Tnew\displaystyle T_{\text{new}}
  4. \(\text{Gain}\)新旧方案的时间差,收益大于0才值得调整。
    Told−(Tcopy+Tnew)\displaystyle T_{\text{old}} - \left( T_{\text{copy}} + T_{\text{new}} \right)

性能衡量工具

优化做得好不好,不能靠感觉,得用秒表和磅秤说话。老张会在园区门口记录每批货物从进来到完成加工的总时间,再对比不同调度方案之间的差值。

衡量数据迁移优化最直观的指标是加速比,也就是优化前后总耗时之比:

除了加速比,老张还会看吞吐量,也就是单位时间能完成多少推理请求;还会看带宽利用率,判断硬件链路是不是被充分占满。如果带宽利用率高但延迟没降,可能是固定开销主导;如果吞吐量上去了但某台设备长期空转,则说明调度顺序仍有优化空间。

S=TserialToverlap\displaystyle S = \frac{T_{\text{serial}}}{T_{\text{overlap}}}
串行执行耗时除以重叠后耗时,得到提速倍数。
  1. \(T_{\text{serial}}\)没有重叠时代码串行跑完的总耗时。
    Tserial\displaystyle T_{\text{serial}}
  2. \(T_{\text{overlap}}\)采用异步通信或流水线后的总耗时。
    Toverlap\displaystyle T_{\text{overlap}}
  3. 比值 \(S\)加速比。数值大于1说明重叠优化有效。
    S\displaystyle S

推理落地实践

最后,老张把一整套物流经验用在了最忙碌的岗位上:在线推理服务。推理请求到了之后,不能先等CPU把数据预处理完、再传给GPU、再等结果传回来,那样每一次请求都会累加延迟。

一个朴素的推理延迟可以拆成三段:

在实际部署中,工程师会用预取流水线改造这段延迟:处理当前请求时,异步拷贝下一个请求的输入;返回当前结果时,让输出回传与后续计算重叠。最终暴露给用户的关键路径几乎只剩下计算时间和极短的尾部传输。

老张站在园区门口,看着货车和加工台如同齿轮般咬合,终于明白:数据迁移并不是拖后腿的杂活,而是一块可以被巧妙藏起来的拼图。谁能在搬运上多花心思,谁的推理服务就能跑得更快。

L=Tinput+Texec+Toutput\displaystyle L = T_{\text{input}} + T_{\text{exec}} + T_{\text{output}}
端到端延迟约等于输入迁移、计算执行和输出回传的时间之和。
  1. \(T_{\text{input}}\)请求数据从CPU迁移到GPU的时间。
    Tinput\displaystyle T_{\text{input}}
  2. \(T_{\text{exec}}\)模型在GPU或加速器上执行计算的时间。
    Texec\displaystyle T_{\text{exec}}
  3. \(T_{\text{output}}\)结果张量从设备侧传回CPU侧的时间。
    Toutput\displaystyle T_{\text{output}}

AI 应用场景

在线推理服务的输入预取流水线

阶段:部署与在线推理

解决的问题:传统服务端串行执行“预处理→拷贝到GPU→推理→拷回CPU”,每次请求都要等待两次完整的数据迁移,延迟高且吞吐低。AI Runtime通过异步流和双缓冲,将输入迁移与上一请求的计算重叠,让GPU尽量不空闲。

具体案例:视觉质检服务每天处理海量工业图片。CPU先完成缩放和归一化,GPU同时推理前一批图片;当GPU忙时,下一张图片已经在锁页内存中准备就绪,等待拷贝引擎悄悄送达。

多卡并行训练中的梯度通信重叠

阶段:分布式训练调度

解决的问题:模型并行或数据并行训练时,各GPU之间要同步梯度。若等反向传播全部结束再做AllReduce,通信会暴露在关键路径上。调度器可以把梯度切分,让一部分梯度先出发,剩余反向计算继续运行。

具体案例:大语言模型训练时,调度器把梯度分成多个切片,在反向传播后半段就启动跨卡通信。通信时间被藏在计算里,整体训练吞吐显著提升。

边缘与云端协同推理

阶段:跨端云资源调度

解决的问题:边缘设备算力有限,云端有大模型但网络带宽不稳定。数据迁移调度器会根据网络状态选择传输压缩中间特征,而不是原始输入;同时用异步通道让上行传输与本地推理重叠。

具体案例:手机端先运行轻量特征提取模型,把特征搬到云端继续跑大模型。上传过程与本地前几层计算同时进行,用户几乎感觉不到远端等待。

Python 演示

python39 行
import asyncioimport time async def copy_tensor(duration):    # 模拟从CPU内存拷贝张量到GPU显存    await asyncio.sleep(duration) async def compute(duration):    # 模拟在GPU上执行一段算子    await asyncio.sleep(duration) async def sync_schedule(iterations, copy_s, calc_s):    # 同步调度:先搬完数据,再执行计算    start = time.perf_counter()    for i in range(iterations):        await copy_tensor(copy_s)        await compute(calc_s)    return time.perf_counter() - start async def pipeline_schedule(iterations, copy_s, calc_s):    # 流水线调度:下一次搬运与本次计算重叠    start = time.perf_counter()    await copy_tensor(copy_s)    next_copy = None    for i in range(iterations):        if i < iterations - 1:            next_copy = asyncio.create_task(copy_tensor(copy_s))        await compute(calc_s)        if next_copy:            await next_copy    return time.perf_counter() - start async def main():    sync_time = await sync_schedule(4, 0.2, 0.3)    pipe_time = await pipeline_schedule(4, 0.2, 0.3)    print(f"sync={sync_time:.2f}s pipeline={pipe_time:.2f}s") if __name__ == "__main__":    asyncio.run(main())
目的:用两个异步函数对比“同步搬运后计算”与“流水线搬运+计算”,直观展示数据迁移重叠带来的提速效果。
  1. await copy_tensor(copy_s)阻塞等待本次搬运完成,模拟同步调度的串行等待。
  2. next_copy = asyncio.create_task(copy_tensor(copy_s))提前发起下一次张量拷贝,模拟异步预取。
  3. await compute(calc_s)计算当前张量,同时下一次搬运正在后台进行。
  4. if next_copy: await next_copy确保下一轮的开始数据已经就绪,相当于流水线尾部汇合。
  5. sync_time` 与 `pipe_time对比结果会显示流水线方式明显更快,因为搬运时间被藏进了计算时间。

常见误区

误区我总以为把模型和输入一次性搬到GPU显存,后面所有算子都自动在GPU上执行,不会再有任何迁移成本。

正解只要计算图中的算子被分配到不同设备,或者中间结果需要从显存拷回CPU,数据迁移就会再次出现。老张的物流园里,货箱不会只搬一次;调度器必须持续关注每一次跨仓动作。

为何常见:因为很多框架封装的API让用户感知不到底层拷贝,初学者容易把“一次显式迁移”误当成“所有迁移”。

误区既然迁移贵,那应该拼命减少拷贝次数,最好一次都不拷。

正解减少拷贝是重要方向,但“少拷贝”不等于“快”。如果一次大拷贝把计算完全阻塞,反而比把拷贝拆小、再藏进计算缝隙更慢。关键在于不要暴露等待时间。

为何常见:人们天然觉得数量少就代价小,却忽略了时间轴上“是否占用关键路径”才是重点。

误区我用上异步拷贝接口后,单个张量的拷贝时间应该也会缩短。

正解异步拷贝不改变单次传输的字节数和带宽,它只是让调用立即返回,让计算不必等待它完成。单次搬运还是那么多时间,但可以被其他工作覆盖。

为何常见:把“接口不阻塞”误解成“硬件跑得更快”,混淆了任务调度与硬件速度。

误区只要数据迁移把PCIe或NVLink带宽打满,就说明优化做到位了。

正解高带宽利用率可能只是说明搬运任务足够多,却未必换来更低延迟或更高吞吐。固定启动开销、依赖链和尾部传输仍可能拖慢整体,所以还要看加速比和端到端延迟。

为何常见:因为带宽看得见摸得着,而延迟收益需要从整体时间线观察,容易被忽略。

面试题

Q1. 数据迁移耗时最简单的估算公式是?

单选题 · easy

  • A.数据量除以带宽← 正确答案
  • B.算力乘以数据量
  • C.带宽乘以数据量
  • D.延迟除以算力

答案:A。数据迁移类似货车运货,时间等于货物量除以搬运速度,即数据量除以有效带宽。其他选项混淆了算力、带宽与延迟的关系。

Q2. 同步调度中,通信阶段计算单元通常处于?

单选题 · easy

  • A.正常运行继续计算
  • B.空闲等待数据到达← 正确答案
  • C.提前预取下一批数据
  • D.自动降低核心电压

答案:B。同步调度里,计算和通信串行执行,搬运期间计算单元只能空转等待,对应物流园里卡车卸货时加工台停工的状态。

Q3. 将内存从可分页改为锁页,直接收益是?

单选题 · easy

  • A.增加显存容量上限
  • B.减少二次拷贝开销← 正确答案
  • C.提高GPU核心频率
  • D.自动消除全部传输

答案:B。可分页内存需要先复制到锁页内存才能被DMA设备访问,锁页内存省去这道中转,让CPU到GPU的拷贝路径更直接。

Q4. 异步流水线加速的核心原因是?

单选题 · mid

  • A.减少实际传输字节
  • B.等待时间被计算填充← 正确答案
  • C.提升数据总线主频
  • D.压缩张量数据格式

答案:B。异步流水线没有让单次搬运变快,而是让搬运与计算重叠,把原来干等的红色空闲段变成绿色通信段,从而缩短关键路径。

Q5. 下面哪项不属于调度器减少迁移开销的手段?

单选题 · mid

  • A.消除不必要的数据拷贝
  • B.将设备放置重新编排
  • C.用更大批量压缩模型← 正确答案
  • D.复用已分配显存池

答案:C。调度器可以通过算子的设备放置、执行顺序、内存池复用和拷贝消除来优化迁移。加大批量并不属于迁移开销优化手段,反而可能让搬运数据量增大。

Q6. 一次拷贝耗时0.2秒,计算耗时0.3秒,重叠后单批最短耗时是?

单选题 · mid

  • A.0.5秒
  • B.0.3秒← 正确答案
  • C.0.1秒
  • D.0.2秒

答案:B。当通信与计算可以并行执行时,关键路径是两者的最大值,即0.3秒。如果串行执行则是0.5秒,这就是重叠优化的价值。

Q7. 推理返回结果时,若输出拷贝未与后续请求计算重叠,主要影响是?

单选题 · hard

  • A.模型精度明显下降
  • B.端到端延迟变长← 正确答案
  • C.训练收敛速度变慢
  • D.显存空间自动翻倍

答案:B。输出回传处在请求链路的尾部,若不与下一请求的计算重叠,它会直接累加到端到端延迟上,类似外卖小哥送完一单才接下一单,整体效率降低。

Q8. 数据迁移的开销来源包含哪些?

多选题 · mid

  • A.传输数据体积← 正确答案
  • B.链路有效带宽← 正确答案
  • C.固定启动开销← 正确答案
  • D.模型训练轮数

答案:ABC。数据体积和有效带宽决定传输耗时,固定启动开销则是每次拷贝都要付的“起步价”。训练轮数与单次数迁移开销无直接关系。

Q9. 调度器可以采用的通信隐藏手段有哪些?

多选题 · hard

  • A.使用异步拷贝接口← 正确答案
  • B.使用双缓冲预取数据← 正确答案
  • C.调整计算与传输顺序← 正确答案
  • D.缩小物理链路带宽

答案:ABC。异步拷贝、双缓冲和顺序调整都能让通信不阻塞计算。缩小物理链路带宽只会让迁移更慢,不是隐藏手段。

Q10. 在推理流水线中,数据预取的目标包括哪些?

多选题 · extreme

  • A.让输入迁移与计算重叠← 正确答案
  • B.让输出回传与后续计算重叠← 正确答案
  • C.提高单位时间请求数← 正确答案
  • D.降低模型存储占用

答案:ABC。预取的核心是让输入和输出迁移都不暴露在关键路径上,从而降低延迟、提高吞吐。模型存储占用属于模型压缩话题,与预取目标无关。