想象你是一家大餐厅的主厨。今天来了六百位客人,菜单上有三道硬菜:蛋炒饭、红烧肉、还有一锅需要炖十小时的佛跳墙。你的后厨里有好几种灶:有宽大的平底锅(一次能摊八个蛋)、有专门炖大菜的深炖锅、还有一排普通小灶。你会怎么安排?聪明的你一定知道:蛋炒饭要用平底锅一次炒一大份;佛跳墙要用深炖锅慢慢炖;普通小灶留给需要灵活调整的菜品。这个“看锅做菜”的过程,就是 AI 编译器(比如 TVM)要做的事情。 在深度学习世界里,程序员写好的模型就像菜谱,而 CPU 和 GPU 就是你的灶台。CPU 的 SIMD 指令集是一口宽平底锅——一条指令同时处理多个数据;GPU 的 Tensor Core 则是专门为矩阵乘法打造的深炖锅——一次指令就能算一小块矩阵。编译器要做的,就是把每一个算子(如卷积、矩阵乘)安排到最合适的“锅”上,还要考虑食材怎么切(数据布局)、灶台怎么分配(线程调度)。这篇文章,我们就走进这家“AI 餐厅”的后厨,看看主厨是怎么做这些决策的。

岗位关联

理解目标硬件特性映射,是写出高性能推理引擎、掌握 TVM 算子调优的底层能力。它能让你从“会写模型”进阶到“让模型跑得飞快”,这是 AI 工程化落地中区分初级与高级工程师的关键分水岭。

目录
  1. 可视化演示
  2. 1. 硬件加速原理
  3. 2. Tensor Core 架构
  4. 3. 内存层级影响
  5. 4. 算子映射策略
  6. 5. 调度原语
  7. 6. 性能优化指标
  8. 7. 硬件抽象层
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示CPU SIMD向量并行执行、GPU Tensor Core矩阵乘累加及AI编译器算子映射流程,交互切换理解深度学习算子到硬件的优化映射。

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

CPU SIMD向量并行 宽向量寄存器一次处理多数据 AVX-512寄存器512位并行 SIMD 单指令多数据提升CPU并行度

1. 硬件加速原理

餐厅里做荷包蛋,新手用单孔小灶一个一个煎,老师傅用大平底锅一次煎八个。CPU 的 SIMD 就是这口大平底锅——它的寄存器很宽(像锅面很大),可以同时容纳多份数据,执行一条指令就完成八个数据的同一操作。这就是“单指令多数据流”的直觉:锅越大,一次处理的菜越多。

在 CPU 上,这种“一条指令处理多份数据”的技术叫 SIMD(Single Instruction, Multiple Data)。你写的循环 for i in range(8): c[i] = a[i] + b[i],编译器可以把它向量化为一条 SIMD 加法指令,同时完成 8 个加法。而 GPU 则走得更远:它用几千个线程并行执行同样的指令,这种模式叫 SIMT(Single Instruction, Multiple Threads,单指令多线程)。要注意,SIMD 不等于多核——多核是几个厨师各炒各的菜,SIMD 是一个厨师用大锅一次炒多份相同的菜。

衡量 SIMD 的加速效果,可以用向量化加速比公式。假设一个任务中可向量化的比例是 p,向量宽度是 w(一次处理 w 个元素):

S=1(1−p)+pw\displaystyle S = \frac{1}{(1 - p) + \frac{p}{w}}

1. 分子 1 是串行执行的总时间基线:把整个任务耗时记作单位 1,这是没有向量化时的基准

Told=1\displaystyle T_{\text{old}} = 1

2. 分母中 (1-p) 是串行部分耗时:p 表示可向量化的比例,剩下的 1-p 只能老老实实逐条指令执行

Tserial=1−p\displaystyle T_{\text{serial}} = 1 - p

3. 分母中 p/w 是向量化后的耗时:向量宽度 w 意味着原来处理 w 份数据的时间现在只够处理一份

Tvec=pw\displaystyle T_{\text{vec}} = \frac{p}{w}

4. 整体加速比是新旧时间之比:分母两项相加就是新方案的期望耗时,用旧耗时除以它就是加速倍数

S=ToldTserial+Tvec=1(1−p)+pw\displaystyle S = \frac{T_{\text{old}}}{T_{\text{serial}} + T_{\text{vec}}} = \frac{1}{(1-p) + \frac{p}{w}}

大平底锅虽好,但如果菜谱里有 20% 的步骤没法一锅炒(比如每份菜需要单独调味),那就算锅再宽,加速也会打折扣。这正是编译器动手优化前,要先“看看菜谱里有多少能一锅炒的部分”的原因。

2. Tensor Core 架构

如果说 SIMD 是宽平底锅,那 Tensor Core 就是后厨里的“佛跳墙专炖锅”。它不炒散菜,只负责炖一种菜——矩阵乘法。你把两锅食材(矩阵 A 和 B)倒进去,它用一组内置的微型算盘一次性算出一小块结果 C。普通灶台要一勺一勺地搅,这口锅直接整块炖熟。

Tensor Core 是 NVIDIA 从 Volta 架构开始在 GPU 中加入的专用硬件单元。普通 CUDA core 一次做一次乘加(scalar),而一个 Tensor Core 硬件单元一次能完成一个 4×4×4 的矩阵乘法运算。调用时由 warp(32 个线程的编组)协作完成,每个线程负责结果矩阵的一小部分。因为计算完全在硬件内部完成,省去了反复搬运数据的开销,所以对矩阵乘法密集的模型(比如 Transformer)特别有效。

一次 4×4×4 矩阵乘法的运算量可以用这个公式刻画:

FLOPs=2×m×n×k\displaystyle \text{FLOPs} = 2 \times m \times n \times k

1. m×n×k 是矩阵乘法的乘加次数:计算 C[m,n] = A[m,k] × B[k,n],需要 m×n×k 次乘法和同样多次加法

Cij=∑t=0k−1Ait×Btj\displaystyle C_{ij} = \sum_{t=0}^{k-1} A_{it} \times B_{tj}

2. 每次乘加计 2 次浮点运算:一次乘法算一次运算,一次加法又算一次运算,所以每对乘加贡献 2

op_count=2\displaystyle \text{op\_count} = 2

3. 总运算量是 2×m×n×k:当 m=n=k=4 时,即 2×4×4×4=128 FLOPs,这就是一个 4×4 张量核单次指令的工作量

FLOPs(4×4×4)=128\displaystyle \text{FLOPs}(4 \times 4 \times 4) = 128

Tensor Core 还常常搭配混合精度:输入用 FP16(半精度)加速计算,累加过程用 FP32 保证精度,避免误差越积越大。这道“佛跳墙”为什么快?因为一切都在锅里面完成,不需要把半成品端出来再倒进去——这也解释了为什么 Tensor Core 对满是矩阵乘法的 Transformer 模型特别友好。

3. 内存层级影响

主厨的后院有个食材库:手边调料架(寄存器)、台面备菜区(共享内存)、后院冷库(全局内存)。取调料几乎不花时间,走一趟冷库却要半天。再会算账的主厨,如果食材搬运太慢,菜也快不起来——计算机系统也是一样。

计算机的内存层级中,寄存器最快但容量极小;GPU 的共享内存是片内高速存储,类似操作台;全局内存容量大但速度慢,类似冷库。深度学习模型动辄上 GB 参数,访存往往才是真正的瓶颈。数据布局(NCHW 还是 NHWC)会影响访存是否连续,布局不对,冷库取货就要走很多冤枉路;访存合并(相邻线程访问相邻地址,合并成一次事务)就是减少“冤枉路”的关键技术。

算术强度衡量每次访存能支撑多少次运算:

I=FB\displaystyle I = \frac{F}{B}

当 I 很低时(比如逐元素相加,读两个数加一次,I≈0.5),性能受限于内存带宽而不是算力。搬数据所需时间的下限是:

tmem=Bb\displaystyle t_{\text{mem}} = \frac{B}{b}

1. 算术强度 I 是运算量除以访存量:每搬 1 字节数据能支撑多少次浮点运算,比值越高说明数据复用越好

I=FflopsBbytes\displaystyle I = \frac{F_{\text{flops}}}{B_{\text{bytes}}}

2. 访存时间下限等于数据量除以带宽:带宽 b 的单位是字节/秒,数据量 B 除以带宽就得到秒数

tmem=Bbytesbbytes/s\displaystyle t_{\text{mem}} = \frac{B_{\text{bytes}}}{b_{\text{bytes/s}}}

3. 整体耗时取计算时间与访存时间的较大者:灶台开火时数据没到就得等,数据到了灶台没空也白搭

T≈max⁡(Tcalc, tmem)\displaystyle T \approx \max(T_{\text{calc}},\ t_{\text{mem}})

所以主厨做菜前先想好“食材怎么摆、一次搬多少”。把搬运细节安排妥当,灶台才能火力全开,不然 GPU 有一半时间在“等菜”。

4. 算子映射策略

六百人的宴席,一道大菜不可能一整锅出。主厨的策略是把食材分成小份,分给几口锅同时炖——这就是分块(Tiling)。AI 编译器面对 4096×4096 的大矩阵乘,同样会切成小块,分给不同的线程块处理。

分块大小不是随便定的:块太大放不进共享内存,块太小又无法隐藏访存延迟。编译器的算子映射策略包括:循环分块(把大循环拆成小循环)、向量化宽度选择(匹配 CPU 的 SIMD 寄存器)、Tile 切分(匹配 GPU 的线程块结构)、线程绑定(决定每个线程计算哪个子块)。

假设分块后 A、B、C 三个子矩阵的大小分别是 t_m×t_k、t_k×t_n、t_m×t_n,分块方案必须满足共享内存容量约束:

(tm×tk+tk×tn+tm×tn)×4≤Sshared\displaystyle (t_m \times t_k + t_k \times t_n + t_m \times t_n) \times 4 \le S_{\text{shared}}

1. A 分块的元素数是 t_m×t_k:要算出 C 的 t_m×t_n 块,需要读到 A 的 t_m 行、t_k 列共 t_m×t_k 个元素

NA=tm×tk\displaystyle N_A = t_m \times t_k

2. B 分块的元素数是 t_k×t_n:同理,B 分块需要 t_k 行、t_n 列

NB=tk×tn\displaystyle N_B = t_k \times t_n

3. C 分块的元素数是 t_m×t_n:这是结果矩阵对应的小块

NC=tm×tn\displaystyle N_C = t_m \times t_n

4. 总字节数不超过共享内存容量:float32 每个元素占 4 字节,三块加起来必须在共享内存放得下

(NA+NB+NC)×4≤Sshared\displaystyle (N_A + N_B + N_C) \times 4 \le S_{\text{shared}}

分块就像备菜盘——每口锅旁边放好一份刚好够用的食材,既不让台面堆满,也不让锅空等。在 CPU 上,分块要匹配 L1/L2 缓存大小;在 GPU 上,Tile 大小还要和线程块(block)里的线程数对齐。

5. 调度原语

主厨有自己的工具箱:切菜刀把大块食材切小,大平底锅负责宽炒,深炖锅负责慢炖。TVM 也有一套“厨具”——调度原语(Schedule Primitive)。原语是编译器里的基础操作,用来把计算图“摆布”成适合特定硬件的形态。

TVM 中最常用的原语包括:split(把循环切小)、fuse(把两个循环合并)、vectorize(标记向量化)、tensorize(替换成硬件张量指令)、parallel(并行化)。这些原语像乐高积木,组合出千变万化的映射方案。关键设计是“调度与实现分离”:同一种算子逻辑(compute)可以通过不同调度(schedule)映射到不同硬件,而输出结果完全一致。

向量化宽度的选择由 SIMD 寄存器位宽决定:

Wvec=LsimdBdtype\displaystyle W_{\text{vec}} = \frac{L_{\text{simd}}}{B_{\text{dtype}}}

1. L_simd 是 SIMD 寄存器位宽:比如 AVX2 是 256 位,SSE 是 128 位,ARM NEON 是 128 位

Lsimd=256 bit\displaystyle L_{\text{simd}} = 256\ \text{bit}

2. B_dtype 是单个元素的位宽:float32 是 32 位,FP16 是 16 位,INT8 是 8 位

Bdtype=32 bit/元素\displaystyle B_{\text{dtype}} = 32\ \text{bit/元素}

3. 相除得到一次能处理几个元素:256÷32=8,正好是 AVX2 一次处理 8 个 float32

Wvec=25632=8\displaystyle W_{\text{vec}} = \frac{256}{32} = 8

tensorize 原语更直接:把一小段计算替换成硬件提供的专用指令(如 Tensor Core 的 mma 指令),相当于告诉后厨“这道菜直接用佛跳墙锅”。工具本身不复杂,复杂的是组合它们的经验——AutoTVM 这类自动调优工具,正是用搜索算法帮你试出最佳组合。

6. 性能优化指标

餐厅开张三个月,老板要复盘:灶台闲置率多高、上菜速度多少、食材搬运时间占比多大。这些指标决定了餐厅能赚多少钱,也决定了算子的优化空间。衡量算子映射质量的核心指标有:算力利用率(实际达到的 FLOPs ÷ 硬件峰值)、内存带宽利用率、以及延迟与吞吐。

Roofline(屋顶线)模型把计算和访存统一起来看,是经典性能分析工具。它画出一条屋顶线:当算术强度低时,性能被带宽“压”在天花板下;当算术强度超过某个转折点后,性能才被算力决定。核心公式:

P=min⁡(Ppeak, I×Bpeak)\displaystyle P = \min(P_{\text{peak}},\ I \times B_{\text{peak}})

1. P_peak 是硬件峰值算力:比如 A100 GPU 在 FP16 Tensor Core 下的理论峰值约 3.1×10^14 FLOP/s

Ppeak=3.1×1014 FLOP/s\displaystyle P_{\text{peak}} = 3.1 \times 10^{14}\ \text{FLOP/s}

2. I×B_peak 是带宽撑起的算力上限:算术强度 I 乘以峰值带宽 B_peak,表示“搬数据的最大能力”对应的算力

Pbandwidth=I×Bpeak\displaystyle P_{\text{bandwidth}} = I \times B_{\text{peak}}

3. 整体取两者较小值:短板决定上限,哪边先到顶就卡在哪里

P=min⁡(Ppeak, Pbandwidth)\displaystyle P = \min(P_{\text{peak}},\ P_{\text{bandwidth}})

当算术强度大于转折点 P_peak / B_peak 时,任务才算计算密集,否则就是访存密集。有了这些 KPI,主厨就能精准定位问题:菜上得慢,到底是锅太小(算力不够)还是食材搬运太慢(带宽瓶颈)?优化不再是玄学,而是有数据支撑的工程决策。

7. 硬件抽象层

连锁餐厅要开分店,每家店的灶台不一样:有的店是大平底锅,有的店是深炖锅。主厨不想给每家店重写一遍菜谱,于是把“灶台能力”抽象成一张标准需求单——告诉每家店:我这道菜需要“宽煎”或“炖煮”能力,你用自己的方式实现。TVM 的硬件抽象层就是这张需求单。

TVM 用 Target 描述硬件特性(比如 llvm -mcpu=skylake 或 cuda -arch=sm_80),后端根据 Target 做指令选择。TVM 的 Intrinsic(内置函数)定义了硬件原生操作,比如 Tensor Core 的 mma.sync.aligned.m16n8k8。这样一来,同一个中间表示(IR)可以被翻译成不同硬件的代码,实现“一处编写,多端部署”。

后端代码生成时,会用一个成本模型做决策:

cost=α×Ninstr+β×Nmem\displaystyle \text{cost} = \alpha \times N_{\text{instr}} + \beta \times N_{\text{mem}}

1. N_instr 是生成的指令条数:指令越多,执行开销越大,在 CPU 上尤其明显

Ninstr=指令条数\displaystyle N_{\text{instr}} = \text{指令条数}

2. N_mem 是访存操作次数:访存是相对昂贵的操作,尤其在 GPU 上全局内存访问动辄几百周期

Nmem=访存次数\displaystyle N_{\text{mem}} = \text{访存次数}

3. α、β 是权重系数:不同硬件上指令与访存的相对代价不同,编译器通过标定这两项来贴合目标硬件

cost=α×Ninstr+β×Nmem\displaystyle \text{cost} = \alpha \times N_{\text{instr}} + \beta \times N_{\text{mem}}

标准菜单让主厨的绝活能被复制到所有分店。硬件抽象层让同一个 AI 模型轻松部署到手机、电脑、服务器和车载芯片上,这正是 TVM 这类编译器的核心价值。

AI 应用场景

推理引擎部署优化

阶段:模型部署 / 推理优化

解决的问题:同一个模型要跑在多种 GPU 上,手动为每个算子适配硬件极其耗时,而且普通工程师很难懂 Tensor Core 的底层调用细节。TVM/TensorRT 这类工具自动识别矩阵乘算子,将其映射到 Tensor Core 并选择 FP16+FP32 混合精度,大幅缩短部署周期。

具体案例:将训练好的 BERT 模型通过 TensorRT 部署到 A100 上。TensorRT 自动把注意力中的矩阵乘替换为 Tensor Core 指令,并把 LayerNorm 融合成单个核函数,推理延迟从 12ms 降到 3.5ms,吞吐提升约 3 倍。

移动端 CPU 向量化优化

阶段:端侧推理优化

解决的问题:手机没有独立 GPU 或 GPU 不可用,必须在 ARM CPU 上跑模型。ARM 的 NEON SIMD 指令集相当于那口“宽平底锅”,可以把逐元素算子(ReLU、量化反量化、逐通道缩放)向量化,榨干移动端算力。

具体案例:在 Android 手机上部署人脸检测模型。用 NEON 指令把逐元素 ReLU 和 INT8 反量化算子向量化(一次处理 4 或 8 个数据),单帧延迟从 50ms 降到 15ms,电池功耗也明显下降。

自动驾驶芯片算子开发

阶段:芯片工具链 / 算子库开发

解决的问题:定制化 AI 芯片往往有专用加速单元,但通用框架(PyTorch/TensorFlow)无法自动利用。需要用编译器把自定义算子映射到特定硬件特性上,同时满足自动驾驶严格的延迟要求。

具体案例:在英伟达 Orin 上,用 TVM 为分割模型生成融合算子。把 Conv+BN+ReLU 三个算子融合成一个核函数,并通过 tensorize 映射到 Tensor Core,端到端延迟降低 40%,满足 L2+ 辅助驾驶的实时性要求。

Python 演示

python81 行
def simd_width(simd_bits, dtype_bits):    """根据 SIMD 寄存器位宽计算一次能处理几个元素"""    return simd_bits // dtype_bits  def arithmetic_intensity(op_type, shape):    """估算算子的算术强度 I = FLOPs / Bytes"""    if op_type == "matmul":        m, n, k = shape        flops = 2 * m * n * k        bytes_read = (m * k + k * n) * 4        return flops / bytes_read    if op_type == "elementwise":        flops = shape[0]        bytes_read = shape[0] * 8        return flops / bytes_read    return 0.0  def roofline_time(flops, peak, band, intensity):    """Roofline 模型估算耗时: 计算时间与访存时间取最大"""    t_calc = flops / peak    t_mem = flops / (intensity * band)    return max(t_calc, t_mem)  class OpMapper:    """把算子映射到目标硬件的决策器"""     def __init__(self, name, simd_bits, peak_flops, bandwidth, has_tc):        self.name = name        self.simd_bits = simd_bits        self.peak_flops = peak_flops        self.bandwidth = bandwidth        self.has_tc = has_tc        self.plan_list = []     def make_plan(self, op_type, shape):        """生成映射方案: 向量化宽度、Tile 大小与是否用 TensorCore"""        wide = simd_width(self.simd_bits, 32)        plan = {            "op": op_type,            "hw": self.name,            "simd_parallel": wide,            "tile": (16, 16, 16) if self.has_tc and op_type == "matmul" else (1, 1, shape[2]),            "use_tensor_core": self.has_tc and op_type == "matmul",        }        self.plan_list.append(plan)        return plan     def estimate_ms(self, op_type, shape):        """估算在该硬件上的执行毫秒数"""        if op_type == "matmul":            flops = 2 * shape[0] * shape[1] * shape[2]        else:            flops = shape[0]        intensity = arithmetic_intensity(op_type, shape)        if self.has_tc and op_type == "matmul":            peak = self.peak_flops        else:            peak = self.peak_flops * 0.1        time_s = roofline_time(flops, peak, self.bandwidth, intensity)        return time_s * 1000.0  def main():    cpu = OpMapper("x86_64-avx2", 256, 1.6e12, 5e10, False)    gpu = OpMapper("A100-tc", 128, 3.1e14, 2e12, True)    shape = (1024, 1024, 1024)     for hw in (cpu, gpu):        plan = hw.make_plan("matmul", shape)        ms = hw.estimate_ms("matmul", shape)        print("硬件:", hw.name)        print("  每指令并行元素:", plan["simd_parallel"])        print("  Tile:", plan["tile"], "使用 TensorCore:", plan["use_tensor_core"])        print("  预估耗时: %.3f ms" % ms)  if __name__ == "__main__":    main()
目的:模拟编译器根据目标硬件特性(SIMD 位宽、是否支持 Tensor Core、峰值算力和带宽)为算子生成映射方案,并用 Roofline 模型预估耗时,直观对比同一矩阵乘在 CPU 与 GPU 上的不同调度策略。
  1. wide = simd_width(self.simd_bits, 32)用寄存器总位宽除以 float32 的 32 位,算出这口“宽锅”一次能炒几个元素。
  2. "tile": (16, 16, 16) if self.has_tc and op_type == "matmul" else (1, 1, shape[2])碰上有 Tensor Core 的 GPU 且算子是矩阵乘时,按 16×16×16 分块下锅;否则用朴素整块方案。
  3. intensity = arithmetic_intensity(op_type, shape)先算算术强度,判断这道菜是“计算量管够”还是“食材搬运费时”。
  4. time_s = roofline_time(flops, peak, self.bandwidth, intensity)用 Roofline 短板模型预估耗时,取计算与访存中更慢的那个。
  5. if __name__ == "__main__":一键跑起来,对比 CPU 与 GPU 的映射决策和预估性能。

常见误区

误区把循环标上向量化后,从 128 位换成 512 位寄存器,觉得速度一定能翻几倍。毕竟“锅宽了嘛”。

正解加速上限受 Amdahl 定律和访存带宽双重限制。如果可向量化比例 p 只有 50%,w 翻倍加速也到不了 2 倍;如果数据在内存里搬不动,CPU 只会“等菜”而不是“炒菜”。性能提升的瓶颈常常不在寄存器位宽,而在内存。

为何常见:纸面上看到 AVX2→AVX512 位宽翻倍,容易拿 512÷256 直接算加速,忽略了真实系统的其他瓶颈。

误区买了带 Tensor Core 的 GPU,觉得所有算子都会自动变快,包括逐元素 ReLU、Softmax、数据归一化这些“小菜”。

正解Tensor Core 只加速矩阵乘法以及能转化为矩阵乘法的算子(如卷积)。逐元素算子访存密集,即使“炖锅再厉害”,也没法减少每一份食材的搬运量,强行使用反而可能因为数据格式转换更慢。

为何常见:厂商宣传 “XX 倍性能提升” 通常基于矩阵乘 benchmark,初学者容易把特定场景的结论泛化到所有算子。

误区NCHW 和 NHWC 只是数据的排列顺序不同,性能应该一样,反正交给 TVM 自动优化就行。

正解在 GPU 上,NCHW 的卷积访存不连续,访存合并效率低;NHWC 对某些算子更友好。布局转换本身也要开销,编译器一般会保留你给定的布局并做局部优化,全局布局选择依然需要工程师的决策。

为何常见:PyTorch 默认用 NCHW,大家习以为常,其实在 TensorFlow 和移动端部署中 NHWC 才是默认,说明硬件对布局的偏好是真实存在的。

误区既然 TVM 能自动搜索调度参数,那我不用理解 SIMD 和 Tensor Core,也能把手里的模型优化得很好。

正解自动调优是在工程师定义的搜索空间里搜索,搜索空间的好坏直接决定结果上限。不懂硬件,你连 candidate 调度都写不出几个,更别说判断搜索结果是否合理。工具只是把经验固化成代码,理解原理才能驾驭工具。

为何常见:工具越来越“傻瓜化”,让人误以为底层知识不再必要。但搜索空间的设计恰恰需要懂硬件的人来定义。

误区做算子优化时,性能不达标就加大并行度或换更强的 GPU,觉得“算力不够就堆算力”。

正解Roofline 模型告诉你,当任务访存密集时,换更强的卡、加并行度收益甚微;应该优化数据复用、提高算术强度。方向和努力同样重要——先判断瓶颈在哪,再决定怎么优化。

为何常见:算力指标(TFLOPS)最显眼,带宽是后台指标,容易被忽视。但真实世界中大多数算子卡在带宽上。

面试题

Q1. CPU 的 SIMD 加速依靠什么硬件特性?

单选题 · easy

  • A.多个 CPU 核并行运算
  • B.宽寄存器装载多份数据← 正确答案
  • C.流水线深度加倍提高
  • D.缓存命中率显著提高

答案:B。SIMD 的核心是宽寄存器(如 YMM 256 位),一条指令对寄存器里的多份数据同时运算。多核并行是 MIMD 不是 SIMD;流水线和缓存是另一类优化手段,与“单指令多数据”无关。

Q2. GPU 的 Tensor Core 专门加速哪类运算?

单选题 · easy

  • A.标量的乘加运算
  • B.矩阵乘法与卷积← 正确答案
  • C.数据的排序归并
  • D.显存的高速拷贝

答案:B。Tensor Core 是 NVIDIA 为矩阵乘法设计的专用硬件单元,卷积可通过 im2col 等方式变成矩阵乘后使用它。标量运算用 CUDA core 即可,排序和拷贝不是 Tensor Core 的目标场景。

Q3. 以下哪些属于 CPU 上的 SIMD 向量化实例?

多选题 · easy

  • A.AVX2 一次处理 8 个 float← 正确答案
  • B.SSE 一次处理 4 个 float← 正确答案
  • C.多核并行执行不同函数
  • D.ARM NEON 处理 4 个 float← 正确答案

答案:ABD。AVX2 是 256 位寄存器处理 8 个 float32,SSE 是 128 位处理 4 个,NEON 是 ARM 的 SIMD 指令集。多核并行是任务级并行(MIMD),不是单指令多数据的 SIMD。

Q4. GPU 中“访存合并”描述的优化技术是哪个?

单选题 · mid

  • A.相邻线程访问相邻地址合并为一次事务← 正确答案
  • B.多个线程共享同一个寄存器文件
  • C.将串行循环合并成向量指令
  • D.用缓存预取隐藏所有访存延迟

答案:A。访存合并指 warp 内相邻线程访问连续地址,硬件把这些访问合并成少数几个内存事务,减少请求次数。C 是循环向量化,B 是寄存器共享,D 是缓存预取,都与访存合并的定义不同。

Q5. 256 位 AVX2 寄存器处理 float32 数据,一次能算几个元素?

单选题 · mid

  • A.4 个元素
  • B.8 个元素← 正确答案
  • C.16 个元素
  • D.32 个元素

答案:B。256 ÷ 32 = 8。float32 是 32 位,256 位寄存器正好装 8 个。这就是向量化宽度的基本公式:寄存器位宽除以元素位宽。

Q6. 以下哪些属于 TVM 的调度原语?

多选题 · mid

  • A.split 循环切分← 正确答案
  • B.vectorize 向量化← 正确答案
  • C.compile 全流程编译
  • D.tensorize 张量化← 正确答案

答案:ABD。split、vectorize、tensorize 都是 TVM Schedule 中用来调整循环结构与映射方式的原语。compile 是最终编译生成代码的流程,不属于调度原语。

Q7. Tensor Core 混合精度计算的典型组合是哪个?

单选题 · mid

  • A.FP32 权重输入 FP16 计算
  • B.FP16 输入输出 FP32 累加← 正确答案
  • C.INT8 权重输入 FP32 累加
  • D.FP64 输入输出 FP64 累加

答案:B。Tensor Core 常用 FP16 做矩阵乘输入,FP32 做累加,兼顾速度与精度。FP32 输入无法利用 Tensor Core 的 2 倍吞吐优势,INT8 是量化场景的另一种组合,FP64 与 Tensor Core 无关。

Q8. 某 GPU 实测 1024³ 矩阵乘耗时 2ms,峰值算力 10 TFLOPS,算力利用率约为多少?

单选题 · hard

  • A.约 10%← 正确答案
  • B.约 35%
  • C.约 70%
  • D.约 95%

答案:A。1024³ 矩阵乘 FLOPs = 2×1024³ ≈ 2.15e9。2ms 内实际算力 ≈ 1.07 TFLOPS,除以峰值 10 TFLOPS ≈ 10.7%。算力利用率 = 实际 FLOPs ÷ 峰值 ÷ 时间。

Q9. 关于 AutoTVM 自动调优,下列说法正确的是哪些?

多选题 · hard

  • A.它自动搜索更优的调度参数← 正确答案
  • B.它可以完全替代工程师定义搜索空间
  • C.它需要使用硬件反馈评估候选方案← 正确答案
  • D.它不需要任何先验硬件知识

答案:AC。AutoTVM 在工程师定义的搜索空间中用模拟退火等算法搜索参数,并用真实硬件测量或成本模型预测来评估候选方案。工程师仍需定义搜索空间,硬件知识决定了搜索空间的质量,因此 B、D 错误。

Q10. 某平台峰值算力 10 TFLOPS、带宽 2 TB/s,计算与访存平衡点的算术强度约为多少?

单选题 · extreme

  • A.5 次/字节← 正确答案
  • B.20 次/字节
  • C.50 次/字节
  • D.200 次/字节

答案:A。Roofline 转折点 = 峰值算力 ÷ 峰值带宽 = 10e12 ÷ 2e12 = 5 次/字节。算术强度低于 5 时被带宽限制,高于 5 时被算力限制。这是判断任务属于“计算密集”还是“访存密集”的关键阈值。