想象你走进一家高级裁缝铺,老师傅给你量体裁衣——肩宽、臂长、腰围精确到毫米,做出来的西装像长在你身上一样合体。可问题来了:每位顾客身材都不同,每来一位新客,裁缝就得从头裁剪一次,费时费力,库房里还堆满了只适合一个人的衣服。这正是“静态形状”编译的处境:编译器为每个固定的输入形状精心特化一份高性能代码,可一旦形状变化,一切推倒重来。 现实中,AI 模型的“顾客身材”千变万化——NLP 服务里句子有长有短,CV 服务里图片分辨率五花八门,推理平台里 batch 大小随请求量波动。如果每种形状都重新编译,线上延迟会突然飙升一个数量级。torch.compile 的“动态形状支持”就像引入弹力面料的设计:一个版型,S/M/L/XL 通吃。编译器用符号变量 s0、s1 代替具体数字,一次编译,任意尺寸输入都能通过,在性能与灵活性之间找到了优雅的平衡点。

岗位关联

学透动态形状支持,你就能理解 AI 编译器如何在“极致性能”与“形状多变”之间智慧取舍——这是模型上线时避免推理服务频繁卡顿的关键能力。

目录
  1. 可视化演示
  2. 动态形状问题
  3. 符号形状分析
  4. 特化与守卫
  5. 静态动态权衡
  6. 配置与使用
  7. 编译图与内核
  8. 部署应用价值
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

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

动态形状问题

想象你走进一家高级裁缝铺,老师傅给你量体裁衣——肩宽、臂长、腰围精确到毫米,做出来的西装像长在你身上一样合体。但问题来了:每位顾客身材都不同,每来一位新客,裁缝就得从头裁剪一次,费时费力,库房里还堆满了只适合一个人的衣服。这就是“静态形状”编译的处境:编译器对每个固定输入形状精心特化一份高性能代码,可一旦形状变化,一切推倒重来。

在真实 AI 部署中,“顾客身材”就是模型输入的形状。NLP 服务中用户句子有长有短,CV 服务中图片分辨率五花八门,推理平台中 batch size 随请求量波动。如果形状只有几种固定值还好,但现实中形状几乎是连续的。用静态编译思路,等于为每个尺寸都做一套西装——成本爆炸,线上服务随时可能因为一个“新尺寸”而延迟飙升。

torch.compile 默认走静态形状路径:它假设输入形状固定,并据此做深度优化。当运行时输入形状与编译时假设不符,就会触发重新编译。想象线上服务突然来了一批长度 127 的句子,而编译时只见过长度 128——现场“赶工裁衣”,延迟直接上一个数量级。我们用公式表示两种模式下的形状变化:

裁缝铺的解决之道是什么?引入弹力面料。一个版型,S/M/L/XL 通吃。编译器同样需要这种“弹力”——这正是动态形状支持的出发点。

x:(B,L,D)⟶y:(B,L′,D′)x:(B, L, D) \longrightarrow y:(B, L', D')
动态形状问题的本质:输入形状中包含多个可变化维度,静态编译无法覆盖所有组合。
  1. 输入张量 x形状由三个维度组成,其中 B 是批次大小。
    x:(B,L,D)x:(B, L, D)
  2. 输出张量 yL 变为 L',代表序列长度在模型中可能改变。
    y:(B,L′,D′)y:(B, L', D')
  3. 静态编译的困局B、L 任一变化都要重新编译,组合爆炸。
    compile_count=∣B∣×∣L∣×∣D∣\text{compile\_count} = |B| \times |L| \times |D|

符号形状分析

裁缝师傅经验丰富,他不再死记每位顾客的厘米数,而是在版型上标注“S/M/L/XL“这样的符号尺码。顾客来了,先归个档——你是 M 码还是 L 码?版型里不用写死”肩宽 42cm“,而是用符号表示”肩宽 = f(尺码)”。这就是动态形状的核心思想:用符号变量代替具体数字。

torch.compile 在捕获计算图后,会做“符号形状分析”:把输入张量的每个维度抽象成符号变量。PyTorch 中默认用 s0、s1、s2 等命名。例如一个三维张量 x,其形状不再是 (8, 128, 64) 这样的常量,而是 (b, s0, d) 这样的符号表达式。编译器可以对这些符号做代数运算,推导出后续所有中间张量的形状表达式,而无需知道具体数值。

符号形状分析的关键价值在于“约束传播”。比如矩阵乘法要求 (b, s0, d) @ (d, s1) 时,编译器能推导出结果形状是 (b, s1),并自动加上约束 s0 == d。这些约束信息会传递给后续的内核选择与图优化阶段,让编译器在不知道确切尺寸时依然能做出合理决策。

这样一来,裁缝的版型上写的不是“腰围 80cm”,而是“腰围 = 弹力松紧”,任何腰围都能穿。符号形状就是编译器版型上的“弹力松紧”。

shape(x)=(b,s0),shape(W)=(s0,d)\text{shape}(x) = (b, s_0), \quad \text{shape}(W) = (s_0, d)
符号形状让编译器有了“代数思维”:不关心具体是 128 还是 256,只要维度关系满足约束,就可以安全编译。
  1. 符号变量 b表示 batch 维的符号,运行时可以是任意正整数。
    shape(x)=(b,s0)\text{shape}(x) = (b, s_0)
  2. 符号变量 s0表示序列长度,来自 NLP 输入的可变维度。
    shape(W)=(s0,d)\text{shape}(W) = (s_0, d)
  3. 约束关系矩阵乘法要求前一个的 s0 与后一个的 s0 相等。
    out=x×W⇒shape(out)=(b,d)\text{out} = x \times W \Rightarrow \text{shape}(\text{out}) = (b, d)

特化与守卫

裁缝铺有了弹力版型后,还需要一个“量体检查”的环节:顾客进门,先快速量一下身材,判断这位顾客的尺寸是否在版型覆盖范围内。如果在,直接拿成衣;如果不在(比如来了个巨人),就得重新裁剪。这个“量体检查”在编译器中就是形状守卫(shape guard)。

torch.compile 编译完成后会生成一系列 guard 条件,记录“这份编译代码适用于哪些形状”。每次模型被调用时,运行时先检查实际输入形状是否满足 guard。满足就走编译好的快速路径;不满足就触发重新编译,生成针对该形状的新代码并加入缓存。下次遇到相同形状,guard 直接命中,无需再编译。

守卫条件的粒度很关键。如果 guard 写得太细(如 batch == 8 且 seq_len == 128),动态性就没了;如果写得太宽(如 seq_len < 100000),又可能丢失特化优化空间。编译器会做权衡:把模型定义的静态维度固定下来,把可能变化的维度放宽成范围条件。

编译缓存:重编译后的新代码会作为新的“成衣”挂上衣架,下次相同尺寸直接取用。这就像裁缝铺积累了一批覆盖各种身材的成衣,顾客越多,命中率越高,服务越快。

正因为有了守卫与重编译机制,动态形状支持才能在“一次编译”与“按需特化”之间灵活切换——这是弹力衣铺能够越开越大的秘密。

guard(x):b∈{1,2,4,8} ∧ 0<s0≤512\text{guard}(x):\quad b \in \{1, 2, 4, 8\} \ \wedge \ 0 < s_0 \leq 512
守卫就是“量体检查”的规则清单:满足条件直接穿衣,不满足则回工坊重做。
  1. guard(x)输入张量运行时触发的一次形状校验函数。
    guard(x):b∈{1,2,4,8}\text{guard}(x):\quad b \in \{1, 2, 4, 8\}
  2. 集合限定batch 维度 guard 只允许四种取值,命中了就用缓存代码。
    ∧ 0<s0≤512\wedge \ 0 < s_0 \leq 512
  3. 范围约束序列长度在 1 到 512 之间合法,超出则触发重编译。
    guard 失败⇒重新编译并更新缓存\text{guard 失败} \Rightarrow \text{重新编译并更新缓存}

静态动态权衡

有了弹力衣,是不是就再也不用量身定做了?并非如此。量身定做的西装在正式场合更挺拔、更合身;弹力衣虽然方便,但版型宽松,做不到极致贴合。这恰是静态编译与动态编译的真实写照。

静态编译的优势在于“极致特化”:编译器知道确切形状后,可以选用最专一的内核,做更激进的算子融合、内存布局优化,甚至捕获 CUDA Graph,把整条计算链路固化成最高效的 GPU 指令序列。而动态编译为了应对任意形状,往往只能选通用内核,还要背负每次调用时 guard 检查的开销,整体性能通常比静态低几个百分点。

实践中最优策略是“混合特化”:把模型定义时就固定的维度(如隐藏层大小、特征维度)按静态处理,把真正会变化的维度(如 batch、序列长度)按动态处理。PyTorch 的 mark_dynamic 正是为此设计——你告诉编译器“只有裤腰要做弹力”,其余部位继续量身定制,兼顾性能与灵活性。

一件完美的弹力西装,应该是“裤腰弹力、肩线挺拔”——静态框架包裹动态维度。聪明的工程师会像好裁缝一样,知道哪些地方该放松,哪些地方该收紧。

Ttotal=Tcompile+∑i=1NTrun(i)+∑j=1MTrecompile(j)T_{\text{total}} = T_{\text{compile}} + \sum_{i=1}^{N} T_{\text{run}}^{(i)} + \sum_{j=1}^{M} T_{\text{recompile}}^{(j)}
动态形状并非免费午餐:它用少量的 guard 开销换取了形状变化时不再频繁重编译的巨大收益。
  1. T_compile首次编译的固定开销,动态模式只需承担一次。
    Ttotal=Tcompile+⋯T_{\text{total}} = T_{\text{compile}} + \cdots
  2. T_run^(i)第 i 次推理的运行耗时,静动态各有不同。
    ∑i=1NTrun(i)\sum_{i=1}^{N} T_{\text{run}}^{(i)}
  3. T_recompile^(j)第 j 次重编译耗时,动态模式下 M 趋近于 0。
    ∑j=1MTrecompile(j)\sum_{j=1}^{M} T_{\text{recompile}}^{(j)}

配置与使用

想给裁缝铺下达“做弹力衣”的指令,只需要一句话:torch.compile(model, dynamic=True)。这是最直接的方式——让编译器把所有维度都视为潜在动态。如果追求更精细的控制,可以用 torch._dynamo.mark_dynamic(x, 0) 只标记输入张量的第 0 维为动态,其余维度继续按静态特化。

dynamic 参数的默认值是 False,也就是说 PyTorch 默认按静态形状编译。这与直觉相反——很多初学者以为框架会自动检测形状变化,但实际上一旦开启编译,如果没有显式声明动态,新形状只能触发昂贵的重编译。显式声明动态维度,就是提前告诉编译器“这位顾客的腰围会变,做弹力处理”。

编译缓存的管理也值得注意。每个 guard 对应的编译产物都缓存在内存中,缓存条目越多,内存占用越大。PyTorch 会评估重编译次数并给出警告,帮助开发者定位“为什么一直在重编译”的问题。合理的做法是:只在真正变化的维度上开启动态,其余保持静态,让缓存条目控制在个位数。

用好这把“弹力开关”,就能让裁缝铺在不同场景间自如切换:客户固定时做定制西装,客户多变时做弹力成衣。

dynamic=True⟺cache={(guardk,codek)}k=1K, K 远小于形状组合数\text{dynamic}=\text{True} \Longleftrightarrow \text{cache} = \{(\text{guard}_k, \text{code}_k)\}_{k=1}^{K}, \ K \text{ 远小于形状组合数}
配置的本质是告诉编译器“弹性边界在哪里”:范围越小,特化越深,性能越好。
  1. dynamic 参数一键开启符号形状模式,所有维度可动态变化。
    dynamic=True\text{dynamic}=\text{True}
  2. 缓存结构每个 guard 对应一份编译代码,K 是缓存条目数。
    cache={(guardk,codek)}\text{cache} = \{(\text{guard}_k, \text{code}_k)\}
  3. K 的意义动态模式下 K 很小(通常 1~2),静态模式下 K 随形状数量膨胀。
    K 远小于形状组合数K \text{ 远小于形状组合数}

编译图与内核

裁缝铺从接单到成衣有一套完整的工序:先画图纸(计算图),再裁剪布料(内核选择),最后缝制(图优化)。动态形状支持贯穿了这三道工序,让弹力设计融入每一步。

首先是“画图纸”:torch.compile 通过 TorchDynamo 将 Python 模型捕获为 FX 计算图。在捕获阶段,编译器记录每个算子的输入输出形状表达式。由于维度是符号变量,图上的形状信息以符号形式存在——这就是符号化的计算图,它可以“流动”到图的所有角落。

其次是“选布料”:动态形状下,编译器内核选择更加谨慎。对于卷积、矩阵乘等核心算子,编译器会优先选择支持任意形状的通用内核(如 cuDNN 中基于符号维度选择算法的路径),而不是针对特定形状硬编码优化的专用内核。符号维度的约束传播在此发挥关键作用——编译器能从约束中推断出对齐、整除等性质,缩小候选内核范围。

最后是“缝制”:图优化阶段,算子融合(如 Conv+BN+ReLU 融合)在符号形状下依然适用,因为融合规则只依赖算子间数据流的形状一致性,不依赖具体数值。动态形状让图优化“去具体化”,反而保证了通用性。

图纸、布料、缝制三个环节都考虑了弹力因素,裁缝铺才能又快又好地服务各种身材的顾客——这就是编译图与内核层面为动态形状打下的地基。

Lout=⌊Lin+2p−ks⌋+1L_{\text{out}} = \left\lfloor \frac{L_{\text{in}} + 2p - k}{s} \right\rfloor + 1
符号形状贯穿「图构建 → 内核选择 → 图优化」全流程,让弹力设计不被任何一道工序卡住。
  1. L_in 与 L_out卷积前后特征图长度,输入长度可变。
    Lout=⌊Lin+2p−ks⌋+1L_{\text{out}} = \left\lfloor \frac{L_{\text{in}} + 2p - k}{s} \right\rfloor + 1
  2. p、k、spadding、kernel size、stride,是模型定义时的超参数。
    p=1, k=3, s=1⇒Lout=Linp=1,\ k=3,\ s=1 \Rightarrow L_{\text{out}} = L_{\text{in}}
  3. 符号代入把 L_in 替换为符号 s0,编译器就能得出输出形状表达式。
    Lout=s0(当 p=1,k=3,s=1)L_{\text{out}} = s_0 \quad (\text{当 } p=1, k=3, s=1)

部署应用价值

弹力衣裁缝铺最终要开成连锁店服务千家万户。动态形状支持在真实部署中的价值,正是让各种输入千变万化的 AI 模型都能流畅运行。

在 NLP 推理服务中,BERT、GPT 等模型的序列长度天然多变。一条查询可能只有 5 个 token,另一条可能长达 500 个 token。没有动态形状支持,每次长度变化都触发重编译,P99 延迟会惨不忍睹。有了动态形状支持,服务端可以放心地对变长输入做 batching,编译一次,全量覆盖。

在 CV 检测部署中,输入图像经过 resize 到不同分辨率(320、416、640 等)后送入模型。静态编译需要为每个分辨率各编一份,显存开销大;动态形状支持让一个编译产物覆盖所有分辨率。与 XLA 相比,PyTorch 的动态形状支持对开发者更透明——XLA 需要显式设置 dynamic_dimension 配置,而 PyTorch 一行 dynamic=True 即可启动。

弹力衣连锁店开进了 NLP、CV 和各类推理平台,让“一座城市的不同身材”都能买到合适的衣服——这就是动态形状支持在部署层面真正的价值。

Speedup=TeagerTcompiled\text{Speedup} = \frac{T_{\text{eager}}}{T_{\text{compiled}}}
动态形状把编译器的通用性提升到生产级:不再依赖形状“恰好固定”,而是拥抱真实世界的多样性。
  1. T_eagerEager 模式(不编译)下的推理耗时,是性能基线。
    Speedup=TeagerTcompiled\text{Speedup} = \frac{T_{\text{eager}}}{T_{\text{compiled}}}
  2. T_compiled编译后的推理耗时,动态模式下也能稳定低于 eager。
    Tcompiled<TeagerT_{\text{compiled}} < T_{\text{eager}}
  3. 动态的贡献编译一次后复用,速度提升且延迟稳定。
    delayP99 不再因形状变化而尖峰\text{delay}_{\text{P99}} \text{ 不再因形状变化而尖峰}

AI 应用场景

NLP 变长序列推理服务

阶段:模型推理部署与性能优化

解决的问题:传统方式下,BERT 等服务收到长度各异的句子,静态编译遇到新序列长度就触发重编译,造成延迟尖峰和 GPU 利用率低下。动态形状支持让一个编译产物覆盖所有序列长度,同时配合变长 batching 把 P99 延迟降低 30% 以上。

具体案例:某对话式 AI 平台的语句向量化服务,输入 token 长度分布在 8~512 之间。开启 torch.compile(model, dynamic=True) 后,单次推理延迟从 8ms 降到 2.5ms,且不再出现因重编译导致的 200ms 毛刺;配合长度分桶,GPU 吞吐提升 2.1 倍。

CV 多分辨率检测服务

阶段:模型优化加速、跨平台部署

解决的问题:YOLO 等检测模型常需在 320×320、416×416、640×640 等不同分辨率下运行。静态编译需要为每种分辨率各占一份缓存和显存,浪费 GPU 资源;动态形状支持让单一编译产物适配全部尺寸。

具体案例:安防摄像头方案中,后端检测服务需同时处理 1080p 和 720p 两路视频流,每路按目标大小动态裁剪。使用动态形状编译后,同一份模型权重同时服务两种分辨率输入,GPU 显存占用减少 40%,切换分辨率零重编译。

多租户 GPU 推理平台

阶段:MLOps 平台建设、资源调度优化

解决的问题:多租户推理平台上,不同用户请求的 batch 尺寸不同,静态编译模式下缓存被无效重编译占满,GPU 算力浪费严重。动态形状支持配合调度器做形状感知排队,大幅提升集群利用率。

具体案例:某云厂商模型托管服务发现,开启动态形状编译后,编译缓存条目从 200+ 降到 3,GPU 平均利用率从 35% 提升到 62%,新模型上线无需预热即可服务各种 batch 请求。

Python 演示

python50 行
import torchimport torch.nn as nnimport time device = "cuda" if torch.cuda.is_available() else "cpu"  class SimpleNet(nn.Module):    def __init__(self, d_in=64, d_hidden=128):        super().__init__()        self.layers = nn.Sequential(            nn.Linear(d_in, d_hidden),            nn.ReLU(),            nn.Linear(d_hidden, d_in),        )     def forward(self, x):        return self.layers(x)  def benchmark(model, batch_sizes, runs=3):    total = 0.0    for bs in batch_sizes:        x = torch.randn(bs, 64).to(device)        for _ in range(runs):            t0 = time.perf_counter()            model(x)            total += time.perf_counter() - t0    return total  base = SimpleNet().to(device)model_dynamic = torch.compile(base, dynamic=True)model_static = torch.compile(base, dynamic=False) sizes = [8, 16, 32, 16, 8, 64, 16, 8]benchmark(model_dynamic, [8], runs=2)benchmark(model_static, [8], runs=2) t_dyn = benchmark(model_dynamic, sizes)t_sta = benchmark(model_static, sizes)print(f"动态形状总耗时: {t_dyn:.4f}s")print(f"静态形状总耗时: {t_sta:.4f}s")print(f"静态/动态 = {t_sta / max(t_dyn, 1e-9):.2f}x") for bs in [3, 7, 11]:    x = torch.randn(bs, 64).to(device)    assert model_dynamic(x).shape == (bs, 64)    assert model_static(x).shape == (bs, 64)print("多种 batch size 下输出形状均正确")
目的:对比同一模型在静态与动态两种编译模式下,面对多种 batch size 时的总耗时差异,直观感受动态形状支持“一次编译、多尺寸复用”的优势。
  1. model_dynamic = torch.compile(base, dynamic=True)开启符号形状编译,所有维度都可动态变化。
  2. model_static = torch.compile(base, dynamic=False)按静态形状编译,遇到新尺寸触发重编译。
  3. benchmark(model_static, [8], runs=2)预热调用,排除首次编译耗时对计时的干扰。
  4. t_sta / max(t_dyn, 1e-9)耗时比大于 1 说明静态模式因重编译拖慢了整体推理。
  5. assert model_dynamic(x).shape == (bs, 64)验证两种模式在任意 batch size 下输出形状都正确。

常见误区

误区我听说 dynamic=True 会引入额外开销,所以动态编译的性能肯定全方位不如静态编译,能不用就不用。

正解动态模式只在“形状频繁变化”的场景下才比静态更优,而它也确实是更快的那一个。静态模式遇到新形状要重编译,这个开销动辄几十毫秒甚至几百毫秒;动态模式一次编译后每次调用只多一次 guard 检查(微秒级)。就像弹力衣虽然不如定制西装贴身,但它帮你省去了每次裁衣的等待时间。当输入形状固定不变时,静态依然更快——关键是场景匹配。

为何常见:很多人把“动态模式单次运行略慢”误解为“总慢”,忽略了重编译的累积成本;而且官方文档确实提到动态模式有额外开销,容易被断章取义。

误区我一开 dynamic=True,模型里所有张量的所有维度就都是动态的了,编译器会为每种形状都生成一套通用逻辑。

正解dynamic=True 的语义是“允许所有维度动态”,但编译器在分析时仍会区分哪些维度实际会变。模型权重、特征维度在计算图中是确定的,编译器照样按静态特化处理。如果只想让个别维度动态(比如只放宽 batch),应该用 torch._dynamo.mark_dynamic 精准标记。就像你把整件衣服做成弹力款,但裁缝还是会区分哪些部位真的需要弹力、哪些部位保持固定裁剪。

为何常见:布尔参数 True/False 容易让人产生“全开/全关”的二元直觉,忽略了编译器内部的精细化分析。

误区动态形状模式下 guard 经常不命中,于是模型还是会被反复重编译,那跟静态模式有什么区别?

正解动态模式的 guard 是“宽”的——它校验的是符号约束(如 s0 在 1~512 区间),而不是具体的某个形状值。所以只要输入尺寸落在约束范围内,guard 始终命中,不会重编译。只有超出约束范围(如序列长度突然超过 512)才会触发重编译,且重编译结果会加入缓存。真正频繁重编译的恰恰是静态模式:每个新尺寸都算“超出约束”。弹力衣的“弹力范围”就是那组宽松的 guard 条件。

为何常见:很多人混淆了“动态模式的守卫”与“静态模式的守卫”,把它们都理解成“精确尺寸匹配”,于是误以为动态模式同样会频繁重编译。

误区我都用 torch.compile 了,编译器应该能自动感知输入形状会变化吧?不用设 dynamic 参数,也不用手动标记任何维度。

正解恰恰相反。torch.compile 的默认策略就是静态形状:你没声明动态,编译器就按“形状固定”去优化。一旦运行中出现新形状,guard 失败并重编译,而且 PyTorch 会打印“Recompiling“警告——这其实是”你没告诉它这是动态”的信号。正确做法是显式 dynamic=True 或用 mark_dynamic 标记变化维度。不告诉裁缝要做弹力裤,他自然会按普通裤子做。

为何常见:框架的“自动化”品牌印象让人误以为所有优化都无需人工介入;实际上编译器需要明确的动态性声明才能做出正确的性能取舍。

面试题

Q1. torch.compile 默认的 dynamic 参数值是?

单选题 · easy

  • A.dynamic 参数默认值为 True
  • B.dynamic 参数默认值为 False← 正确答案
  • C.dynamic 参数默认值为 None
  • D.dynamic 参数默认值为 auto

答案:B。torch.compile 默认 dynamic=False,即按静态形状编译优化。想启用动态形状需显式传 dynamic=True,或用 mark_dynamic 标记特定维度。就像裁缝默认按量体尺寸做衣服,不会主动做弹力设计。

Q2. 动态形状支持要解决的核心问题是什么?

单选题 · easy

  • A.降低模型的推理精度损失
  • B.避免为每种输入尺寸重新编译← 正确答案
  • C.减少模型参数量以节省显存
  • D.提高模型在训练时的收敛速度

答案:B。静态编译下每种新形状都要重新编译,线上延迟飙升。动态形状用符号变量表示维度,一次编译即可适配多种输入尺寸,本质是避免重编译成本。

Q3. 符号形状分析中,张量的可变维度通常用什么表示?

单选题 · mid

  • A.用具体整型常量表示全部维度
  • B.用符号变量 s0、s1 表示可变维度← 正确答案
  • C.用浮点数近似表示维度大小
  • D.用字符串拼接表示维度信息

答案:B。具体常量是静态形状的思路;浮点数和字符串无法参与编译器内部代数推导。符号变量 s0/s1 让编译器能表达维度关系,例如 s0+1、2×s0 等约束传播。

Q4. torch.compile 处理动态形状的机制包括哪些?

多选题 · mid

  • A.生成形状守卫来校验运行时形状← 正确答案
  • B.守卫失败时触发重新编译← 正确答案
  • C.用符号维度做形状约束传播← 正确答案
  • D.自动将模型权重转为 float16

答案:ABC。动态形状依赖 guard 校验运行时形状、符号维度约束传播以及重编译回退机制。D 是量化范畴,与形状支持无关。三者的配合类似“量体、判断、回工坊”的完整流程。

Q5. 动态形状编译中运行时 guard 校验失败后会发生什么?

单选题 · mid

  • A.直接抛出异常并终止推理过程
  • B.触发一次针对新形状的重新编译← 正确答案
  • C.忽略 guard 继续使用旧代码执行
  • D.自动降低输入张量的维度大小

答案:B。guard 失败说明当前输入形状超出已编译代码的适用范围,编译器会为该形状重新编译一份代码并缓存。后续遇到相同形状就直接命中缓存,这与裁缝为特殊身材顾客重新裁剪后留下版型是同一逻辑。

Q6. 相比动态形状,静态形状编译的主要优势是什么?

单选题 · hard

  • A.能适应更多输入尺寸而无需重编译
  • B.编译器能做更激进的内核特化优化← 正确答案
  • C.编译时间更短且不需要缓存管理
  • D.不需要生成任何形状守卫代码

答案:B。静态形状下编译器知道确切尺寸,可选用专一内核、做算子融合和 CUDA Graph 捕获等深度优化,性能通常高于动态模式。动态模式为了通用性会增加 guard 检查和通用内核开销。

Q7. NLP 模型部署中,哪些维度常见为动态变化?

多选题 · mid

  • A.batch 维度随请求量波动变化← 正确答案
  • B.序列长度随句子长短变化← 正确答案
  • C.词表大小随分词器配置变化
  • D.embedding 维度随模型版本变化

答案:AB。NLP 中 batch 和序列长度天然多变;词表与 embedding 维度在模型定义时就固定了,属于静态维度。变长场景正是动态形状支持的核心战场。

Q8. 只想标记某一维度为动态,应该使用哪个 API?

单选题 · mid

  • A.torch.compile 的 dynamic 列表参数
  • B.torch._dynamo.mark_dynamic← 正确答案
  • C.torch.set_grad_enabled 禁用梯度
  • D.torch.no_grad 禁用梯度计算

答案:B。mark_dynamic 可在编译前把输入张量的指定维度标记为动态,让 guard 只放开该维度,其余维度仍按静态特化。B、C、D 与形状控制无关,后两者是梯度控制 API。

Q9. 关于动态形状与 CUDA Graph 的关系,下列说法最准确的是?

单选题 · extreme

  • A.动态形状下 CUDA Graph 完全无法使用
  • B.CUDA Graph 捕获要求静态形状必然失败
  • C.动态模式下 CUDA Graph 仍需 guard 命中具体形状才能回放← 正确答案
  • D.CUDA Graph 能自动适配任意动态输入形状

答案:C。CUDA Graph 本质是捕获一套固定形状的 GPU kernel 调用序列。动态形状下,只有 guard 命中了某个已捕获的具体形状,图回放才有效;形状一变就得重新捕获。所以实践中 CUDA Graph 常与静态特化配合,而非直接用于纯动态路径。

Q10. 启用 dynamic=True 后,下列哪项是最直接的收益?

单选题 · easy

  • A.模型推理精度得到显著提升
  • B.内存占用降低到原来的一半
  • C.输入形状变化时无需重新编译← 正确答案
  • D.训练时梯度自动变为稀疏表达

答案:C。dynamic=True 的核心收益是形状变化时复用同一份符号编译结果,避免反复重编译的延迟尖峰。精度与显存不受该参数直接影响,梯度稀疏也与此无关。