想象你是一家连锁餐饮集团的后厨总管。同一道招牌菜(神经网络模型),要在总店大厨房、商场分店、街边小摊(服务器、手机、嵌入式设备)这些条件完全不同的后厨里做出来,而且客人等不了太久。你不能改菜谱——模型结构不能动,能改的只有后厨的干活方式。TVM 的部署优化技巧,就是一套“后厨提效手册”:把切菜、配菜、炒菜合并到同一个灶台(图融合);让锅碗瓢盆轮流复用,少买新锅(内存复用);把大盆大罐换成精巧小碟,菜量不变但占地方更小(量化部署);把招牌菜的备料流程提前锁进柜子,客人一点就拿出来(缓存策略)。这套玩法不碰菜谱一个字,却能显著提高出菜速度。本文就带你在 TVM 的后厨里转一圈,看看一台模型部署到任何设备上时,如何从上菜 5 分钟提速到 1 分钟。

岗位关联

掌握部署优化技巧,是 AI 工程师把模型从“能跑”推向“跑得好”的最后一公里,也是工程落地与面试中最高频考察的硬功夫。

目录
  1. 可视化演示
  2. 图级别融合
  3. 内存复用
  4. 量化部署
  5. 缓存策略
  6. 编译调优
  7. 部署验证
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

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

图级别融合

老主厨做一道鱼香肉丝,原来要切完菜把半成品端回冷藏室,再拿出来配菜,配完又端回去,最后再拿出来炒——每换一个工序,食材就要在储藏室(内存)和灶台(计算单元)之间跑一趟。图级别融合就是干脆把三个灶台拼成一个中央工作站:食材从冰箱拿出来一次,就地完成切、配、炒,直接出锅,中途不再回冷藏室。

在 TVM 的计算图(Relay IR)里,每个算子就是一道工序。融合之前,每完成一个算子,中间结果张量都要写回内存,下一个算子再把它读出来,一来一回全是时间。TVM 的 FuseOps 编译 pass 会在编译期自动分析算子间的依赖关系,把可以合并的算子划进同一个融合组,最终编译成一个 kernel(融合内核)。整个过程模型源码一字未改。

经典融合模式主要有三类:逐元素链(add → relu → mul 这类 element-wise 算子)、卷积链(conv2d + bias_add + relu,几乎每张 CNN 都有)、以及输出端融合(把最后的 sigmoid/softmax 并入前一个算子)。融合的收益可以量化——中间结果不再落地主存,而是留在寄存器或缓存里直接流转。

R=MbeforeMafter=N\displaystyle R = \frac{M_{before}}{M_{after}} = N

1. 融合前访存次数:N 个算子各自独立执行时,每个算子都要读一次输入、写一次输出,S 为单张量大小,总访存是 2N 次。

Mbefore=2NS\displaystyle M_{before} = 2NS

2. 融合后访存次数:所有算子合并为一个内核,只读一次原始输入、写一次最终输出,共 2 次。

Mafter=2S\displaystyle M_{after} = 2S

3. 访存减少倍数:两者相除,系数 2S 约掉,得到融合带来的访存节省倍数就是算子个数 N。

R=2NS2S=N\displaystyle R = \frac{2NS}{2S} = N

中央工作站让食材少跑路,融合后内核启动次数也从 N 次变成 1 次——这就是后厨提效的第一板斧,也是部署优化最划算的起步动作。

内存复用

后厨里最占地方的往往不是食材,而是一柜子锅碗瓢盆。如果不做规划,每道工序都买一套新锅,后厨迟早堆不下。内存复用就是“锅碗轮流用”:A 菜的火候结束了,锅腾出来直接给 B 菜用——只要两道菜不会同时开火就行。

推理时每个算子都需要输入和输出缓冲区。朴素做法是每个算子运行前 malloc 一块、结束后 free,结果峰值内存高、碎片多。TVM 的图运行时(graph runtime)改用静态内存规划:在编译期就分析每个缓冲区从分配(live-in)到释放(live-out)的生命周期,然后做“生命周期着色”。

生命周期不重叠的缓冲区,可以被映射到同一块物理内存,这和编译器做寄存器分配是同一套思路。实际的 runtime 会维护一个工作池(workspace pool),算子需要的临时缓冲都从池里取,用完归还,避免反复向操作系统申请内存。整个图最终只需要支撑峰值时刻的内存,这就是峰值内存优化。

Mpeak=max⁡t∑b∈live(t)sb\displaystyle M_{peak} = \max_{t} \sum_{b \in \text{live}(t)} s_b

1. 生命周期定义:每个缓冲区 b 从分配时刻 a_b 到释放时刻 d_b 之间存活,据此定义任意时刻 t 的存活缓冲区集合。

live(t)={b∣ab≤t≤db}\displaystyle \text{live}(t) = \{ b \mid a_b \le t \le d_b \}

2. 同刻占用量:把当前存活的所有缓冲区大小 s_b 加起来,就是 t 时刻的内存占用。

S(t)=∑b∈live(t)sb\displaystyle S(t) = \sum_{b \in \text{live}(t)} s_b

3. 峰值内存:遍历整个推理过程,S(t) 的最大值就是内存复用后所需物理内存的理论下界,规划分配只要满足它即可。

Mpeak=max⁡tS(t)\displaystyle M_{peak} = \max_{t} S(t)

只要规划得当,同时开火的锅数就是内存峰值。TVM 通过复用让整张图的显存占用从“每层都买新锅”变成“同一时刻最多几口锅”,这正是边缘设备能跑起大模型的底气。

量化部署

满汉全席用脸盆上菜还是用精致小碟?菜量(信息量)不变,换成小碟,一个蒸笼能装四倍数量的菜。FP32 就是脸盆——每个数占 4 字节;INT8 是小碟——只占 1 字节。同样的内存带宽,一次能搬运四倍的数据;支持 INT8 的硬件单元,计算也能快 2 到 4 倍。

量化部署把浮点权重和激活映射到低比特整数。TVM 生态通过 relay.quantize 等 pass 在编译期自动插入量化与反量化节点,模型源码照样不用改。最常用的是仿射(affine)量化,它把浮点区间均匀切成 256 个档位,每个值落进最近的档位,再映射成 int8 整数。

q=round(rS)+Z,S=rmax−rmin255\displaystyle q = \text{round}\left(\frac{r}{S}\right) + Z, \qquad S = \frac{r_{max} - r_{min}}{255}

1. 浮点数值范围:原始数值 r 落在区间 [r_min, r_max] 内,区间宽度决定了量化需要覆盖的动态范围。

r∈[rmin,rmax]\displaystyle r \in [r_{min}, r_{max}]

2. 缩放系数:int8 共有 256 个档位、255 个间隔,把浮点区间宽度除以 255,得到每个档位代表的数值长度。

S=rmax−rmin255\displaystyle S = \frac{r_{max} - r_{min}}{255}

3. 量化取整:r 除以 S 得到档位编号,round 做四舍五入,Z 作为零点把档位平移到 int8 的有效范围。

q=round(rS)+Z\displaystyle q = \text{round}\left(\frac{r}{S}\right) + Z

换小碟之后,同一趟上菜能端四倍数量的盘子——这就是量化四倍体积压缩和可观提速的秘密。实践中还要用校准数据集(一批有代表性的真实输入)统计激活的数值范围,避免被离群点带偏;对卷积权重还可以做 per-channel 量化,每个通道单独定标,把精度损失压到 1% 以内。ARM 的 dotprod 指令、x86 的 VNNI、NVIDIA 的 TensorRT 都在等着吃 int8 这口饭。

缓存策略

连锁店的招牌菜每天要做几百遍,难道每遍都从洗菜切菜开始?聪明的店会把菜谱、腌好的肉、调好的酱汁锁进备料柜——同一道菜一个电话就能端出来。第一次贵在备菜,之后全是取用。TVM 部署里最贵的“备菜”就是编译。

编译一次模型要经历图优化、自动调优、代码生成,动辄几十秒甚至几十分钟。但只要模型结构不换、目标设备不换、调优配置不换,编译产物(动态库 .so、调优记录)完全可以跨进程、跨会话复用。这就是编译产物缓存:第一次 build 落盘,之后直接 load 进内存,绕开整条编译流水线。

缓存键的设计是灵魂。缓存键至少要包含模型结构哈希、输入 shape、目标平台 target、调优配置版本,否则可能把 A 模型的产物错配给 B 模型使用——这比没有缓存更危险。缓存命中率越高,平均部署时延越低。

Tavg=H⋅Thit+(1−H)⋅Tmiss,H=NhitNhit+Nmiss\displaystyle T_{avg} = H \cdot T_{hit} + (1 - H) \cdot T_{miss}, \qquad H = \frac{N_{hit}}{N_{hit} + N_{miss}}

1. 命中率定义:命中的次数占总请求次数的比例,分子是命中次数,分母是命中加未命中的总次数。

H=NhitNhit+Nmiss\displaystyle H = \frac{N_{hit}}{N_{hit} + N_{miss}}

2. 两种开销对比:命中时只需反序列化 so 文件,未命中则要执行完整编译,两者相差一到两个数量级。

Thit≪Tmiss\displaystyle T_{hit} \ll T_{miss}

3. 加权平均时延:按命中率对两种耗时加权,得到带缓存的期望部署时延。

Tavg=H⋅Thit+(1−H)⋅Tmiss\displaystyle T_{avg} = H \cdot T_{hit} + (1-H) \cdot T_{miss}

备料柜虽然不显眼,却把“从洗菜开始”变成“开柜即用”。在生产环境里,编译缓存是支撑模型快速迭代和灰度发布的后厨标配。

编译调优

后厨人手增加一倍,出菜速度会快一倍吗?不会——备料、装盘、端菜这些环节只有一个人能做(串行部分),灶台和水槽的数量也有限。厨师越多,收益越小,人挤在一起反而手忙脚乱。编译调优要解决的就是“加多少人、怎么分工”的问题。

TVM 编译时通过 target 字符串指定线程数、向量化宽度等选项,例如 llvm -mcpu=skylake -num-cores=4 -vector-width=8,ARM 设备上则写 llvm -mcpu=cortex-a72 -num-cores=4。向量化指令让一条 CPU 指令同时处理多个数据,相当于一个厨师同时颠四口锅。再配合 autoTVM / Ansor 自动调优器,用进化搜索为每个算子找到最优的 tile 大小、展开方式和向量化方案。

Amdahl 定律刻画了并行加速的天花板:加速比取决于可并行部分占比 f 和线程数 p,串行部分永远拖后腿。

Sp=1(1−f)+fp\displaystyle S_p = \frac{1}{(1-f) + \dfrac{f}{p}}

1. 并行比例:f 表示整个任务中可并行部分的时间占比,取值在 0 到 1 之间。

0≤f≤1\displaystyle 0 \le f \le 1

2. 串行耗时:串行部分的时间占比是 1 - f,这部分无论加多少线程都原样保留。

Tserial=(1−f)⋅T1\displaystyle T_{serial} = (1-f) \cdot T_1

3. 加速比公式:并行部分时间从 f·T_1 降到 f·T_1/p,总耗时变小,用原耗时除以新耗时即得加速比。

Sp=T1(1−f)⋅T1+f⋅T1p=1(1−f)+f/p\displaystyle S_p = \frac{T_1}{(1-f)\cdot T_1 + \frac{f \cdot T_1}{p}} = \frac{1}{(1-f) + f/p}

聪明的后厨不是无限招人,而是先看清哪些环节能并行、哪些必须串行,再决定预算花在哪。自动调优器就是在替我们做这件事——它试出每种分工方式的耗时,挑出最优解。实践中还要注意,内存带宽受限的算子加线程几乎无收益,因为瓶颈在搬数据不在算,这就要用 roofline 思维去判断。

部署验证

分店开张不能只看菜谱好看,要真刀真枪试营业。验证就是试营业:测高峰时上菜要多快(端到端时延)、口味和总店是否一致(精度对比)、各分店设备不一样但菜量不能缩水(多设备调度)。没有测量,一切优化都是玄学。

TVM 提供 graph_executor 的 benchmark 能力:先预热若干轮,再统计单次推理延迟,画出 p50 / p99 分位值,同时计算吞吐量。精度方面,用一组固定输入(黄金样本)对比优化前后输出与 fp32 参考结果的误差,常用最大绝对误差和余弦相似度。

多设备调度则解决“一套模型多处跑”的诉求:每个目标平台单独编译产物,或者用 heterogeneous executor 把不同算子分派给 CPU / GPU / NPU,各干各的擅长活。

Throughput=NTtotal\displaystyle \text{Throughput} = \frac{N}{T_{total}}

1. 测试窗口:预热完成后,记录从第一次推理开始到第 N 次推理结束的总耗时。

Ttotal=tN−t1\displaystyle T_{total} = t_N - t_1

2. 吞吐量定义:完成的样本数除以总耗时,反映服务整体的处理能力。

Throughput=NTtotal\displaystyle \text{Throughput} = \frac{N}{T_{total}}

3. 时延约束:把全部时延排序,取第 99 百分位的值作为 p99,要求它低于业务约定的 SLA 红线。

Lp99≤LSLA\displaystyle L_{p99} \le L_{SLA}

试营业通过,才敢把“后厨提效手册”推广到所有分店。优化的每一步都要用数字说话:融合省了多少访存、复用降了多少峰值、量化掉了多少精度、缓存命中率达到多少——一切可测量,一切可回滚。

AI 应用场景

边缘设备实时检测部署

阶段:模型编译与部署优化(图融合 + 量化 + 内存复用)

解决的问题:边缘盒子算力弱、内存小,直接部署 fp32 模型单帧推理 200ms 以上且经常内存溢出。传统做法是换更大内存的硬件,成本高、功耗大。AI 方案是在编译期应用图融合减少访存、量化把模型压到 int8、静态内存规划把峰值压下来,从而在原有硬件上跑通。

具体案例:某智慧安防厂商把 YOLOv5s 部署到 RK3588 边缘盒子,用 1000 张真实场景图做校准数据集完成 INT8 量化。推理时延从 220ms 降到 48ms(约 4.5 倍),峰值内存从 520MB 降到 160MB,mAP 仅从 0.632 降到 0.625。

云服务多版本编译缓存

阶段:服务化部署与 CI/CD 流水线

解决的问题:CTR 推荐模型日级迭代,每次更新都要重新编译模型,耗时半小时以上,回滚还要再等半小时。传统做法是让工程师熬夜等编译完成。AI 方案是用缓存键管理编译产物,未变更的模型直接命中缓存,只对增量版本做编译。

具体案例:某广告平台以“模型结构哈希 + target + 调优版本”作为缓存键,90% 的灰度版本命中历史编译产物,平均上线时间从 35 分钟压缩到 8 分钟,回滚更是秒级完成。

多端统一性能验证

阶段:跨平台部署验证

解决的问题:同一套模型要跑安卓手机、x86 服务器和 ARM 网关,各设备性能差异大,出了问题难以定位是模型问题还是设备问题。传统做法是各端各自优化、各自测试,口径不一。AI 方案是统一 benchmark 脚本:同一组黄金样本、同一套 p99 与吞吐指标、按 target 分别编译产物。

具体案例:某 IoT 厂商在验证中发现某款 ARM 小核设备 p99 异常高,通过统一指标对比定位到线程亲和性配置错误,调整 num-cores 与调度策略后时延恢复与同型号设备一致。

Python 演示

python65 行
"""量化部署与编译缓存演示: 模拟 TVM 部署优化流程"""import hashlibimport randomimport time  def symmetric_quantize(values, bits=8):    """对称量化: q = round(v / scale), 适合权重与激活"""    upper = 2 ** (bits - 1) - 1    scale = max(abs(v) for v in values) / upper    q = [max(-upper, min(upper, round(v / scale))) for v in values]    return q, scale  def fp32_infer(weights, x):    """全精度推理: 普通浮点乘累加"""    return sum(w * t for w, t in zip(weights, x))  def int8_infer(q_w, scale_w, q_x, scale_x):    """int8 推理: 整数乘累加, 最后统一缩放回浮点"""    acc = sum(a * b for a, b in zip(q_w, q_x))    return acc * scale_w * scale_x  def cache_key(model, target, version):    """缓存键: 模型结构哈希 + 目标平台 + 调优版本"""    payload = repr(model) + "|" + target + "|" + str(version)    return hashlib.sha256(payload.encode()).hexdigest()[:16]  def compile_model(target):    """模拟 TVM 编译: 高耗时操作"""    print("[compile] targeting:", target)    time.sleep(1.0)    return "lib_deploy_" + target.split(" ")[1] + ".so"  def main():    random.seed(42)    weights = [random.uniform(-1, 1) for _ in range(16)]    x = [random.uniform(-1, 1) for _ in range(16)]    target = "llvm -mcpu=cortex-a72"    store = {}     key = cache_key(weights, target, 1)    if key not in store:        store[key] = compile_model(target)    print("第一次部署, 编译产物:", store[key])     q_w, scale_w = symmetric_quantize(weights)    q_x, scale_x = symmetric_quantize(x)    out8 = int8_infer(q_w, scale_w, q_x, scale_x)    out32 = fp32_infer(weights, x)    err = abs(out8 - out32) / abs(out32)    print("int8 结果: %.4f" % out8)    print("fp32 结果: %.4f" % out32)    print("相对误差: %.4f" % err)     key2 = cache_key(weights, target, 1)    print("二次部署命中缓存:", key2 in store)  if __name__ == "__main__":    main()
目的:用纯 Python 模拟 TVM 部署优化的两个核心动作——INT8 量化推理和编译产物缓存,展示量化误差与缓存命中逻辑。
  1. scale = max(abs(v) for v in values) / upper用最大绝对值定标,保证量化后不超出 int8 上限
  2. q = [max(-upper, min(upper, round(v / scale))) for v in values]四舍五入并截断到合法范围
  3. acc = sum(a * b for a, b in zip(q_w, q_x))整数乘累加比浮点乘累加更快更省电
  4. return acc * scale_w * scale_x最后一次性统一缩放,避免中间反复转浮点
  5. payload = repr(model) + "|" + target + "|" + str(version)缓存键必须包含模型、平台、版本三要素
  6. if key not in store: store[key] = compile_model(target)缓存未命中才执行昂贵的“编译”
  7. key2 = cache_key(weights, target, 1)相同键再次查询,验证二次部署命中缓存

常见误区

误区我以为图融合要手动把几个算子写进同一个自定义层,比如把 Conv 和 ReLU 合并成一个函数,改完模型结构还得重新训练。

正解融合是编译器在编译期自动完成的前端图变换。TVM 的 FuseOps pass 会分析算子依赖并生成融合组,模型源代码结构完全不变,只是最终生成的 kernel 被合并了。相当于后厨菜谱一个字没改,只是把三个灶台拼成一个工作站。

为何常见:因为很多人最早接触的是手工算子融合教程,误以为这是唯一方式。现代编译器普遍提供自动融合 pass,手工操作早已不是主流。

误区int8 只有 256 个档位,肯定比 float32 差很远,精度损失至少 5% 以上,线上根本不敢用。

正解量化把浮点范围按统计分布压缩到 256 个档位,配合校准数据集和 per-channel 缩放,实际部署中精度损失通常控制在 0.5% 到 1% 以内;部分任务甚至因为量化的正则效应精度不降反升。

为何常见:把“随手 round 截断”和“精心校准的量化”混为一谈。前者确实掉点严重,后者是在了解数据分布之后的有损压缩。

误区我开 32 个线程跑推理,肯定比 4 个线程快 8 倍,加满核心数就对了。

正解Amdahl 定律说明串行部分决定加速上限,且线程增多会带来调度开销和内存带宽竞争,实际加速曲线先升后平甚至下降。正确做法是用 benchmark 扫一遍线程数,找到拐点再定配置。

为何常见:日常编程中并行库用多了,容易直觉认为核数和速度是线性关系。AI 推理往往受内存带宽约束,算力并非瓶颈,加线程帮不上忙。

误区部署缓存不就是把推理结果存起来,同一个请求来了直接返回,省得再算一遍吗?

正解部署优化的缓存核心是编译产物缓存——把 .so 模型库和调优记录按缓存键落盘,避免每次启动或每次更新都重新编译。它和业务结果缓存是两回事:前者省的是编译时间,后者省的是推理计算。

为何常见:因为“缓存”一词在系统里太常见,容易把编译器缓存和业务缓存混为一谈,忽略了编译才是部署中最耗时的环节。

面试题

Q1. 关于 TVM 图融合,以下说法正确的是?

单选题 · easy

  • A.融合会改变网络结构,需要重新训练
  • B.融合能减少内核启动与中间访存开销← 正确答案
  • C.融合就是把 float 改成 int8 精度
  • D.融合只能在 GPU 设备上生效执行

答案:B。图融合是编译期的自动图变换,模型结构不变也无需重训,A 错;量化才是改精度,C 错;融合与设备类型无关,D 错。融合的核心收益正是减少 kernel 启动次数和中间结果访存。

Q2. 内存复用中“生命周期不重叠”的含义是?

单选题 · easy

  • A.两个缓冲区从未同时存活← 正确答案
  • B.两个缓冲区大小完全相同
  • C.两个缓冲区类型一定一致
  • D.两个缓冲区位置紧密相邻

答案:A。生命周期指缓冲区从分配到释放的时间区间。不重叠意味着两个区间没有交集,因此可以安全映射到同一块物理内存。大小、类型、相邻位置都不是复用的必要条件。

Q3. INT8 量化公式 S = (r_max - r_min) / 255 中 255 的含义是?

单选题 · mid

  • A.int8 档位间隔的个数← 正确答案
  • B.模型的卷积层数之和
  • C.输入张量的维度总数
  • D.训练迭代的总轮次数

答案:A。int8 共有 256 个取值、255 个间隔,所以把浮点区间等分成 255 份,每份长度就是缩放系数 S。其余选项与量化公式无关,属于干扰项。

Q4. 部署量化时校准数据集的主要作用是?

单选题 · mid

  • A.参与训练更新网络的全部权重
  • B.统计激活值范围以确定缩放系数← 正确答案
  • C.扩大模型容量提升泛化能力
  • D.缩短编译时间降低部署成本

答案:B。校准数据集不参与训练,而是用真实输入跑一遍前向,统计激活值的实际分布,从而确定 scale 和 zero point。它不改变模型容量,也不会影响编译时间。

Q5. 编译产物缓存键设计中必须包含哪类信息?

单选题 · mid

  • A.模型结构哈希与目标平台配置← 正确答案
  • B.服务器 IP 与磁盘剩余空间
  • C.开发人员的姓名与工号
  • D.测试集标签与损失曲线

答案:A。缓存键必须能唯一标识“编译产物是谁、为谁编译”。模型结构哈希决定产物内容,目标平台决定指令集与调度,二者缺一不可。IP、人名、测试标签与产物身份无关。

Q6. 根据 Amdahl 定律,当串行比例 f 固定时,并行加速的理论上限是?

单选题 · hard

  • A.1/(1-f) 是并行加速的理论上限← 正确答案
  • B.1-f 表示永远无法提升的部分
  • C.f/p 表示加速后的并行耗时
  • D.1/f 表示串行部分的总耗时

答案:A。当线程数 p 趋于无穷时,f/p 趋于 0,加速比 S_p 趋近 1/(1-f)。B 是串行比例本身,C 是并行部分加速后的耗时,D 是并行比例的倒数,均不是加速比上限。

Q7. 为什么量化部署后还必须做精度对比?

单选题 · hard

  • A.因为量化会改变硬件时钟频率
  • B.因为需要验证误差是否可接受范围← 正确答案
  • C.因为量化能提高模型参数量
  • D.因为误差只影响训练不影响推理

答案:B。量化是有损压缩,必须用黄金样本对比量化前后输出与 fp32 参考的误差,确认业务指标在可接受范围。量化不改变时钟频率,也不会增加参数量,误差对训练和推理都有影响。

Q8. 部署验证时常用的性能指标有哪些?

多选题 · mid

  • A.p50 与 p99 推理时延← 正确答案
  • B.单位时间处理样本吞吐量← 正确答案
  • C.峰值内存占用量数值← 正确答案
  • D.源码行数与注释数量

答案:ABC。部署验证关注时延分位数、吞吐量和峰值内存,这些直接反映推理效率与资源占用。源码行数和注释数量与运行性能无关,属于代码工程范畴。

Q9. 下列哪些手段可以降低量化精度损失?

多选题 · hard

  • A.使用校准数据集确定量化范围← 正确答案
  • B.采用 per-channel 逐通道缩放← 正确答案
  • C.训练阶段加入量化感知训练← 正确答案
  • D.直接舍弃激活值的量化结果

答案:ABC。校准数据集让缩放系数贴合真实分布,per-channel 让每个通道独立定标,量化感知训练让权重适应低比特误差,三者都是成熟手段。直接舍弃激活量化结果会丢失关键信息,不可取。

Q10. 关于 TVM 图融合,下列说法正确的是?

多选题 · easy

  • A.可融合逐元素算子链整体← 正确答案
  • B.可融合卷积-偏置-ReLU 链← 正确答案
  • C.融合能减少内核启动次数← 正确答案
  • D.融合必须手工改写模型结构

答案:ABC。逐元素链和 conv-bias-relu 都是 TVM 自动融合的经典模式,融合后 kernel 数量减少、启动开销降低。融合由 FuseOps pass 自动完成,无需手工改写模型结构,D 错。