想象你经营着一家定制工坊,门口排着长长的订单队列。工坊里有三位手艺迥异的师傅:老C(CPU)是全能手艺人,开料、雕刻、抛光样样会,但做事一次只能埋头几件,速度上不来;老G(GPU)是流水线大师,遇到一千个一模一样的圆珠打磨任务,能拉出一条上千人的传送带同时开工,快得惊人,可你要是让他雕一朵不对称的花,他反而抓瞎;老N(NPU)是AI雕塑专家,对神经网络模型里层层叠加的矩阵造型术研究到了极致,功耗低、效率高,但离开AI手艺,他几乎不碰别的活。 你的职责,就是当那位“总调度”:每来一张订单(计算任务),判断该给谁做(设备选型)、排到几点(执行时序)、把什么材料送到谁的工位(数据搬移)、几个师傅同时闲着时怎么让工坊产出最大化(负载均衡)。这一整套“分单-派活-送货-催工期”的运营艺术,正是异构计算调度的核心命题。本篇文章,我们就以这家“异构工坊”为镜,把 CPU/GPU/NPU 调度的门道一项项拆开讲给你听。

岗位关联

学懂异构计算调度,你将掌握 AI Runtime 系统的核心枢纽,理解主流框架性能差异的根源,是走向编译器、系统软件和 AI 基础架构方向的关键一跃。

目录
  1. 可视化演示
  2. 异构计算概览
  3. 调度器架构
  4. 调度策略
  5. 数据搬运优化
  6. 算子调度实践
  7. 性能调优实战
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示CPU/GPU/NPU异构调度:任务按设备特性调度、负载占比分配、异构加速对比单核性能,通过交互理解异构计算资源的调度机制。

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

设备调度:按特性分配 任务按设备特性调度 NPU适合专用算子 J→{CPU,GPU,NPU} 分配到最合适设备

异构计算概览

工坊刚开张时,你可能觉得“谁的活儿谁干就行”。可真拿到订单你会发现,一张“给一万个圆珠打磨抛光”的订单,让全能手艺人老C干三天三夜也赶不完;而一张“精雕一只花纹复杂的椅子腿”的订单,让流水线老G来做,他会摆摆手说做不了。硬件生而不同,这就是“异构”的由来。

在计算世界里,CPU 是全能型选手:核心少、主频高、擅长处理分支和复杂控制逻辑;GPU 是众核并行狂魔:成百上千个核心,适合做数据高度并行的矩阵运算;NPU 则是 AI 专用加速器:把卷积、矩阵乘、激活函数这些深度学习基本操作固化成计算引擎,精度要求放宽但极快极省电。三者共存一台机器,就构成了“异构计算系统”。

异构计算系统要高效运转,关键在“调度”:任务分给谁、什么时候分、数据怎么搬、谁先谁后,这些决策都发生在 AI Runtime 系统里。调度得好,工坊产能翻倍;调度不好,设备闲置、搬运拥堵、总时间翻倍。延迟、吞吐、能耗都是调度的核心指标。为了量化一次任务的整体代价,我们常用下面这个时间模型:

认识了工坊里的三位师傅,你才不会把打磨订单错派给木匠。接下来,我们推门走进“调度室”,看看总调度手边的排单系统和工具台长什么样。

Ttotal=Tdispatch+Ttransfer+TcomputeT_{total} = T_{dispatch} + T_{transfer} + T_{compute}
一句话:任务总耗时 = 调度决策耗时 + 数据搬运耗时 + 硬件计算耗时。
  1. 调度决策耗时 $T_{dispatch}$总调度看订单、想对策、拍板派活的时间,任务越复杂、设备越多,这个时间越长。
    TdispatchT_{dispatch}
  2. 数据搬运耗时 $T_{transfer}$把原料从仓库搬到对应工位的时间,料越大、通道越窄,搬运越慢。
    Ttransfer=DdataBbandwidthT_{transfer} = \frac{D_{data}}{B_{bandwidth}}
  3. 硬件计算耗时 $T_{compute}$工匠真正开干的时间,取决于活儿的总量和师傅的手速。
    Tcompute=WworkPdeviceT_{compute} = \frac{W_{work}}{P_{device}}

调度器架构

工坊开工后,你这个总调度不可能一直站在门口发号施令。你需要一间“调度室”:前台接单(任务提交)、排期看板(任务队列)、师傅档案簿(设备资源表)、以及发号指令的传令台(执行引擎)。整个调度室,就是 Runtime 调度器的缩影。

AI Runtime 系统是连接上层框架(PyTorch / TensorFlow)和底层硬件的桥梁。用户编写模型代码时并不关心设备,框架把计算表示成一张计算图,图里的每个节点就是一个“算子及其参数”。Runtime 调度器拿到计算图后,把它们封装成一个个“任务”(也常被称为 kernel / 算子实例),放进任务队列等待派发。

调度器的核心模块包括:任务队列(FIFO 或优先级队列)、设备池(记录每个设备的算力、显存、状态)、调度策略模块(决定下一个任务给谁)和执行引擎(真正把 kernel 提交到设备驱动、管理 Stream 和事件同步)。当一个任务被派给 GPU,执行引擎会调用 CUDA / OpenCL 等驱动 API 将 kernel launch 到设备上,这个过程涉及上下文切换和内核启动开销,调度器必须精打细算。一次调度的完整开销可表示为:

调度室搭好了,接单、看板、设备档案、传令台一应俱全。可总调度每天还得面对同一个灵魂拷问:到底按什么规则派单?这正是下一节的“调度策略”。

Tschedule=Twait+Tdispatch+TlaunchT_{schedule} = T_{wait} + T_{dispatch} + T_{launch}
一句话:调度总开销 = 排队等待 + 派单决策 + 启动内核三部分。
  1. 排队等待 $T_{wait}$任务在队列里排队的时间,队列越拥挤、前面的任务越重,等待越久。
    TwaitT_{wait}
  2. 派单决策 $T_{dispatch}$调度策略模块根据设备负载、数据位置等信息选择目标设备的时间。
    TdispatchT_{dispatch}
  3. 内核启动 $T_{launch}$执行引擎把 kernel 提交到驱动并启动硬件执行的时间,通常受 API 调用开销限制。
    TlaunchT_{launch}

调度策略

同样是派单,有的总调度喜欢“前一天晚上就把明天的活排好”,有的则“现场看谁闲着就派给谁”。前者是静态调度,后者是动态调度。选择哪种风格,直接决定了工坊应对突发订单的能力。

静态调度(Static Scheduling)在编译期或启动前完成:遍历计算图,测量每个算子在目标设备上的预估耗时、内存占用,然后一次性排定执行顺序。它的优点是运行开销小、确定性强;缺点是一旦运行时的输入 shape、设备负载与预估不符,排好的计划就可能不再最优,甚至引发显存不足。

动态调度(Dynamic Scheduling)在运行期实时决策:每个算子在执行前根据当前设备的空闲率、数据所在位置、优先级等进行判断,选择最快的路径。这天然适合在线推理、多租户等负载波动大的场景。负载均衡策略是动态调度的核心——但请注意,负载均衡不等于任务数量均分,而要权衡设备算力(FLOPS)、带宽、内存、能耗,通常用带权重的贪心选择或最小负载优先算法。设备利用率这个指标常用来衡量负载均衡的好坏:

派单规则定了,原料运送也不能掉链子。订单派给了流水线大师老G,可料还在仓库没搬过去,老G也只能干瞪眼。下一节我们看总调度如何打通“原料运输线”。

Uavg=∑i=1NtibusyN×TwindowU_{avg} = \frac{\sum_{i=1}^{N} t_i^{busy}}{N \times T_{window}}
一句话:平均利用率 = 所有设备忙的时间总和 ÷(设备数 × 时间窗)。
  1. 忙时 $t_i^{busy}$第 i 台设备在时间窗内真正做计算的时间,等待搬运和同步空闲都不算。
    tibusyt_i^{busy}
  2. 设备总数 $N$可参与调度的设备数量,例如 1 块 CPU + 1 块 GPU + 1 块 NPU,则 N=3。
    NN
  3. 时间窗 $T_{window}$考察利用率的时间长度,通常取一个性能分析周期。
    TwindowT_{window}

数据搬运优化

工坊里最让师傅们头疼的,往往不是活难,而是“料没到”。CPU 的“原料仓库”是主存(DDR),GPU 和 NPU 的仓库是各自的显存(HBM),中间只靠一条细细的 PCIe 总线连接。料在仓库搬一趟的时间,可能比师傅干活的时间还长。

异构计算中,数据搬运开销是性能的头号杀手。一个数据搬运周期的耗时可近似为数据量除以总线带宽。以 PCIe 4.0 x16 为例,理论带宽约 32GB/s,而 GPU 内部 HBM 带宽可达 TB/s 级,相差数十倍。如果每次 kernel 执行都先把数据从主存拷入显存、算完再传回,整体吞吐会被严重拉低。

优化手段可归为三大类:第一,减少搬运次数——算子融合(fusing),把多个算子合成一个 kernel,中间结果留在显存里不再回主存;第二,零拷贝 / 统一寻址——通过统一虚拟地址(UVM)、设备直传(P2P)、GPUDirect RDMA 等技术让数据在设备间直通,不经过 CPU 内存中转;第三,计算与通信重叠——用多流(Streaming)将数据预取和计算放到不同流水线上并发执行,使总耗时从加法关系变成取最大值:

原料运输线打通了,工坊速度快了不少。但订单千变万化——有些订单是“先雕花、再打磨、再组装”的连环工序,总调度得把一整条流程拆成一个个算子,动态编排给合适的师傅。这就是“算子调度实践”。

Teff=max⁡(Tcompute,Ttransfer)T_{eff} = \max(T_{compute}, T_{transfer})
一句话:只要搬运和计算能重叠进行,整体耗时取两者中较大者,而不是相加。
  1. 计算耗时 $T_{compute}$设备在当前 kernel 上做计算的时间。
    TcomputeT_{compute}
  2. 搬运耗时 $T_{transfer}$与当前计算并发进行的数据预取或回传时间。
    TtransferT_{transfer}
  3. 取最大 $\max$重叠执行时,较长的那一项决定整体耗时,另一项被完全“藏”在它的背后。
    max⁡(a,b)\max(a, b)

算子调度实践

现实中的订单往往不是孤立的活,而是整条流水线:比如一张“AI 雕塑”订单,要经历“切料→粗磨→雕刻→精修→上色”五道工序。对应到计算世界,一个深度学习模型就是一张计算图,图中每个节点(Conv、MatMul、ReLU、Softmax)都是一个算子。

算子调度的第一步是“算子映射”:Runtime 为每个算子选择目标设备上可执行的 kernel 实现。例如矩阵乘在 GPU 上可调用 cuBLAS 库,在 NPU 上可调用 CANN 的 MatMul 算子;某些算子在某设备上没有实现,就需要“回退”到 CPU 执行,或用其他算子组合代替。第二步是“图优化”:在算子执行前,调度器会对计算图做改造——算子融合(如 Conv+BN+ReLU 合并成一个 kernel)、常量折叠(把固定的计算提前算好)、内存复用规划(显存池)等。

第三步是“多流执行”:把没有依赖关系的算子放入不同 Stream,利用设备的多队列并发执行。最终执行时间取决于“关键路径”——即最慢的那条流。硬件适配则要求 Runtime 对不同厂商的 AI 芯片提供统一算子接口,屏蔽底层差异。多流执行的整体耗时可以写作:

掌握了算子级调度,你已经是位经验丰富的总调度了。不过要让工坊营收真正翻倍,还得学会“体检”——找出到底哪个环节在偷懒、哪个环节在堵车。这就是性能调优。

Tgraph=max⁡s∈streams∑k∈sTkT_{graph} = \max_{s \in streams} \sum_{k \in s} T_k
一句话:多流执行时,整张计算图的耗时等于最慢那条流上的算子耗时之和。
  1. 流 $s$一条 CUDA Stream(或对应 NPU 的 Task Stream)代表一个逻辑执行序列,不同流之间可以并行。
    s∈streamss \in streams
  2. 算子耗时 $T_k$流中第 k 个算子的执行时间。
    TkT_k
  3. 流维度的最大值整张图要等所有流都完成,所以总耗时取决于最慢的流。
    max⁡s\max_{s}

性能调优实战

总觉得工坊产能不对劲,但说不清是哪个师傅慢、哪条运输线堵,还是派单规则有漏洞。跟经营工坊一样,异构系统的性能调优第一步永远是“先测量、后动手”,而不是盲目改参数。

常用的性能分析方法包括:借助 profiling 工具(NVIDIA Nsight、昇腾 Profiler、Perf)记录每个 kernel 的执行时间、搬移时间、空闲等待时间;通过时间线(timeline)视图,可以直观找出 GPU 利用率的“空洞”在哪里。定位瓶颈的常用手法是分类讨论:计算密集型问题(设备利用率高但耗时仍长)→ 增大并行度或优化算法;搬运密集型问题(大量时间花在数据传输上)→ 算子融合和流水线重叠;调度开销问题(kernel 很小但数量很多)→ 合并小 kernel、减少启动次数。

系统性调优手段可归纳为:提高并行度(增大 batch、多卡并行)、提高数据局部性(缓存友好、减少跨设备访问)、异步化(异步推理、流水线并行)。Amdahl 定律告诉我们,即使把可并行部分做到极致,整体加速比仍然受串行部分瓶颈制约——所以不要忽略调度器本身的开销和同步等待。加速比公式如下:

从认识三位工匠,到搭起调度室,再到练就整套派单、运输、编排、体检的本领——你这家异构工坊终于开始高效运转。剩下要做的,就是把理论搬到真实战场,用代码和工具打磨成工程利器。

S=1(1−p)+pmS = \frac{1}{(1-p) + \dfrac{p}{m}}
一句话:并行加速比受串行比例 p 制约,串行部分哪怕只占 5%,加速比天花板也只有 20 倍。
  1. 串行比例 $p$程序中可并行部分占整体时间的比例,那么不可并行部分就是 $1-p$。
    1−p1-p
  2. 并行度 $m$并行执行时使用的设备或核心数量,m 越大,并行部分耗时越低。
    pm\frac{p}{m}
  3. 总加速比 $S$优化后的耗时相对原耗时的倍数,由串行部分决定天花板。
    S=ToldTnewS = \frac{T_{old}}{T_{new}}

AI 应用场景

深度学习训练框架的多流调度

阶段:模型训练阶段的计算编排

解决的问题:训练模型时,CPU 负责数据预处理,GPU/NPU 负责张量计算。传统同步执行会让加速器频繁空转等待数据。Runtime 调度器把数据增强、前向传播、反向传播算子放入不同流,按依赖关系动态派发,并用数据预取和梯度累积填充设备空闲时间。

具体案例:PyTorch 在 GPU 上训练 CNN 时,DataLoader 通过多进程预取数据,调度器将 Forward/Backward 算子下发到 GPU 流,CPU 同步执行数据增强;若使用昇腾 NPU,CANN 的 Runtime 同样会做算子调度与流同步。这种编排通常能让训练吞吐相比朴素逐算子同步执行提升 2-5 倍。

大模型推理服务的动态路由

阶段:在线推理服务部署与请求调度

解决的问题:在线 LLM 服务中,prefill 阶段计算密集,适合 NPU/GPU;decode 阶段访存密集且延迟敏感。如果所有请求固定走一条路径,会互相拖慢,且设备利用率不均。

具体案例:vLLM 等推理引擎实现 continuous batching,Runtime 调度器根据当前请求的 KV Cache 占用和设备负载,动态把请求组装成 batch 分配到不同设备;CPU 专门负责 tokenizer、采样与后处理,NPU 做矩阵运算,GPU 处理并行度高的解码计算。端到端吞吐相比静态 batch 方案提升约 2-3 倍。

Python 演示

python90 行
"""异构计算调度模拟器模拟标量/矩阵/神经网络三类任务在 CPU/GPU/NPU 上的调度与执行。"""  class Task:    def __init__(self, tid, kind, workload):        self.tid = tid        self.kind = kind        self.workload = workload        self.device = None        self.remain = workload  class Device:    def __init__(self, name, speed, kinds):        self.name = name        self.speed = speed        self.kinds = kinds        self.running = None     def can_run(self, kind):        return kind in self.kinds     def start(self, task):        self.running = task        task.device = self.name     def tick(self, dt):        if self.running:            self.running.remain -= self.speed * dt            if self.running.remain <= 0:                self.running.remain = 0                self.running = None     def idle(self):        return self.running is None  class Scheduler:    def __init__(self, devices):        self.devices = devices        self.queue = []     def submit(self, task):        self.queue.append(task)     def dispatch(self):        for task in list(self.queue):            for device in self.devices:                if device.can_run(task.kind) and device.idle():                    device.start(task)                    self.queue.remove(task)                    break     def run(self, dt=0.1):        t = 0.0        while self.queue or any(not d.idle() for d in self.devices):            self.dispatch()            for device in self.devices:                device.tick(dt)            t += dt        return t  def main():    cpu = Device("CPU", 1.0, {"scalar"})    gpu = Device("GPU", 8.0, {"matrix", "scalar"})    npu = Device("NPU", 16.0, {"neural", "matrix"})    scheduler = Scheduler([cpu, gpu, npu])     tasks = [        Task(0, "scalar", 4.0),        Task(1, "matrix", 40.0),        Task(2, "neural", 32.0),        Task(3, "matrix", 24.0),        Task(4, "scalar", 2.0),        Task(5, "neural", 8.0),    ]    for task in tasks:        scheduler.submit(task)     total = scheduler.run()    print(f"总耗时: {total:.1f} 秒")    for task in tasks:        print(f"任务{task.tid}({task.kind}) -> {task.device}")  if __name__ == "__main__":    main()
目的:演示一个小型异构调度器如何在 CPU/GPU/NPU 之间按类型分派任务,并模拟执行得到总耗时。
  1. cpu = Device("CPU", 1.0, {"scalar"})定义设备算力和支持的任务类型,CPU 只跑标量任务。
  2. if device.can_run(task.kind) and device.idle():调度核心判断:设备既支持该类型又空闲才派活。
  3. task.remain -= self.speed * dt每个模拟步按设备算力扣减任务剩余量,算力越快完成越快。
  4. device.start(task)把任务分配给设备,并记录任务落在了哪个设备上。
  5. while self.queue or any(not d.idle() for d in self.devices):只要队列非空或有设备还在忙,调度就继续推进。
  6. total = scheduler.run()启动整个调度模拟,返回所有任务完成的总时间。

常见误区

误区AI 计算不就是 GPU 吗?NPU 和 GPU 差不多,反正都是做并行计算的加速芯片,选哪个都行。

正解GPU 为图形渲染和大规模并行而设计,擅长矩阵乘法等通用并行计算,但通用计算仍要遵循 SIMT 编程模型;NPU 则把深度学习的卷积、矩阵乘、激活等算子直接固化为专用计算引擎,在低精度、高能效比上有优势,但通用性远不如 GPU。选型要结合算子类型、精度要求和部署场景,不能一概而论。

为何常见:两者都常被当作“AI 加速器”出现在宣传中,且都能跑神经网络,容易让人忽略架构差异。

误区把所有任务平均分给 CPU、GPU 和 NPU,每个设备各干各的,这样总该叫负载均衡了吧?

正解负载均衡的目标是让各设备利用率趋近一致,而不是让任务数量相等。一个矩阵乘任务在 CPU 上要 10 秒、在 GPU 上只要 1 秒,平均分配只会让 CPU 成为瓶颈。正确做法是按设备算力加权分配,并根据运行时的利用率数据动态调整,让快设备承担更多任务。

为何常见:日常生活中的“平均分”最直观公平,但忽略了异构设备在能力上的巨大差异。

误区现在都支持统一虚拟内存了,数据会自动搬来搬去,零拷贝早就由硬件实现了,用户不用管。

正解统一虚拟地址(如 CUDA UVM)只是在软件层屏蔽了地址空间差异,底层仍会发生按页迁移,缺页时同样有巨大开销。真正的零拷贝需要主动优化:用 cudaMemPrefetchAsync 预先将数据放到目标设备,用 P2P 让 GPU 之间直传,再用多流把搬运和计算重叠,才能把搬运开销“藏”起来。

为何常见:厂商强调“统一内存”的易用性,让初学者误以为数据位置和搬移开销从此不再是问题。

误区调用 kernel 的 API 立刻返回了,说明 GPU 已经开始算了,异步操作不用再管同步。

正解异步只是把 launch 指令提交到驱动队列后立即返回,kernel 可能还排在队列里未执行。如果后续算子依赖前一个算子的结果,必须通过 Stream 事件或 cudaStreamSynchronize 做同步;否则会出现数据未就绪就执行,导致错误或隐性的性能等待。

为何常见:看到 API 立即返回,误以为硬件已经执行完毕;实际“提交”和“执行完成”是两件完全不同的事。

面试题

Q1. 在异构计算系统中,最适合执行大规模矩阵乘法任务的计算设备是?

单选题 · easy

  • A.CPU中央处理器
  • B.GPU图形处理器← 正确答案
  • C.NPU神经网络器
  • D.固态硬盘存储

答案:B。矩阵乘法是典型的大规模并行密计算,GPU 拥有数千个核心,是执行这类任务的主流选择。CPU 核心少更擅长复杂控制流,NPU 更偏 AI 模型整体加速但通用性不如 GPU,固态硬盘则根本不是计算设备。

Q2. NPU 最适合加速哪一类负载?

单选题 · easy

  • A.网页渲染请求
  • B.磁盘文件读写
  • C.神经网络推理← 正确答案
  • D.操作系统核心

答案:C。NPU(神经网络处理器)把卷积、矩阵乘、激活等算子固化为专用引擎,天然为深度学习的训练和推理优化。网页渲染更依赖 GPU 的并行管线,磁盘读写是存储控制器的职责,操作系统核心主要运行在 CPU 上。

Q3. 以下哪项是调度器的主要职责?

单选题 · easy

  • A.编译高级代码
  • B.分配计算任务← 正确答案
  • C.设计神经网络
  • D.训练数据标注

答案:B。调度器负责把计算图中封装的算子任务分配到合适的设备上执行,包括排队、决策和 kernel 启动。编译高级代码是编译器的工作,设计网络结构是算法工程师的工作,数据标注更是完全不同的环节。

Q4. 静态调度的核心特点是什么?

单选题 · mid

  • A.运行期实时决策
  • B.编译期预先决定← 正确答案
  • C.依赖人工干预
  • D.无需任何信息

答案:B。静态调度在编译期或程序启动前,根据计算图、算子预估耗时和内存占用一次性排定执行顺序,运行开销小、可确定性强。运行期实时决策是动态调度的特征,人工干预和无需信息都不符合静态调度的工程实质。

Q5. 数据搬运开销大的根本原因是什么?

单选题 · mid

  • A.设备算力太低
  • B.总线带宽有限← 正确答案
  • C.任务数量太多
  • D.编译器效率差

答案:B。CPU 主存与 GPU/NPU 显存之间通过 PCIe 等总线相连,总线带宽远低于设备内部的存储带宽,导致搬运大数据成为瓶颈。算力低、任务多、编译器效率差都不是数据搬移慢的根本原因。

Q6. 使用多个 CUDA Stream 的主要目的是?

单选题 · mid

  • A.降低编程难度
  • B.实现任务并发← 正确答案
  • C.减少代码体积
  • D.提升网络带宽

答案:B。多个 Stream 可以让无依赖关系的 kernel 和数据拷贝在不同队列上并行执行,从而实现计算与通信重叠、提高设备利用率。编程难度不会因此下降,代码体积和网络带宽与 Stream 数量没有直接关系。

Q7. 以下哪项属于负载均衡的关键目标?

单选题 · mid

  • A.任务数量完全相等
  • B.设备利用率接近← 正确答案
  • C.所有设备全速运行
  • D.单设备算力最大化

答案:B。负载均衡追求各设备利用率尽量一致,这样系统总吞吐最高。任务数量相等可能让快设备吃得太少、慢设备排队;所有设备全速运行只是美好愿望,实际受依赖关系约束;单设备算力最大化是硬件设计目标,与调度均衡无关。

Q8. Amdahl 定律说明的核心结论是?

单选题 · hard

  • A.并行核心越多越好
  • B.串行部分限制加速← 正确答案
  • C.内存越大性能越高
  • D.编译优化可达十倍

答案:B。Amdahl 定律指出加速比上限由串行部分比例决定,即使并行部分无限加速,总体加速比也有天花板。并行核心并非越多越好,内存容量和编译优化都不在 Amdahl 定律的讨论范围内。

Q9. 计算与通信重叠后,总耗时近似等于?

单选题 · hard

  • A.计算时间加通信时间
  • B.计算和通信的较大者← 正确答案
  • C.通信时间的二倍
  • D.计算时间的两倍

答案:B。当数据搬运和计算分别位于不同流水线时,两者并发进行,整体时间由较长的一方决定,即取 max(计算,搬运)。相加关系是未重叠时的情形,二倍的说法没有依据。

Q10. 常见的零拷贝优化手段包括哪些?

多选题 · mid

  • A.统一虚拟地址空间← 正确答案
  • B.使用缓存一致协议
  • C.编程语言运行时
  • D.设备间直连传输← 正确答案

答案:AD。统一虚拟地址(如 CUDA UVM)可减少显式拷贝,设备间直连传输(P2P、GPUDirect RDMA)让数据不经 CPU 中转,两者是典型零拷贝手段。缓存一致性协议属于硬件内存模型范畴,编程语言运行时与零拷贝无直接关系。

Q11. 动态调度相比静态调度的优势包括?

多选题 · mid

  • A.可感知负载变化← 正确答案
  • B.无需编译期分析← 正确答案
  • C.降低运行时开销
  • D.支持突发任务到达← 正确答案

答案:ABD。动态调度在运行期实时决策,能感知设备负载和任务突发,且不需要完整的编译期 profiling 分析。但它的劣势恰是运行时开销更大,C 选项把劣势说成优势,属于黑话陷阱。

Q12. 关于算子融合的意义,下列说法正确的是?

多选题 · hard

  • A.减少 kernel 启动次数← 正确答案
  • B.消除中间结果回传← 正确答案
  • C.一定降低计算精度
  • D.缩短数据搬运总量← 正确答案

答案:ABD。算子融合把多个算子合成一个 kernel,减少了启动次数,中间结果留在片上显存而不回主存,从而缩短搬移总量。融合本身不改变精度,C 说法错误。