面包店老板“面包叔”最近有点焦虑——隔壁新开了家网红烘焙店,出包速度惊人,自己店里明明也升级了设备,却说不清到底快在哪。徒弟阿吉搬来一台“智能烤炉”,告诉师父:别急着下结论,咱们先设计一场公平的“烘焙比武”:用同样的面粉、同样的面团、同样的室温,旧炉子烤20炉,新炉子也烤20炉,分别记录时间、数清每炉产量、盯住厨房台面堆了多少盘子,最后算算新炉子到底快了几倍。 这个故事里的“比武方案”,就是JAX性能基准测试要做的事。JAX是当下热门的AI框架,它的JIT编译和XLA优化能让深度学习训练和推理明显变快。但“快了多少”不能靠感觉,而要像这场烘焙比武一样:设计公平的对照实验、多次重复取均值、把编译时间和执行时间分开计时、用FLOPS衡量算力、用内存分析排查爆显存、最后算出加速比。这篇文章会带你完整走一遍这套流程,让你从一个只会“跑模型”的人,变成能“用数据说话”的性能优化高手。

岗位关联

对AI从业者而言,掌握性能基准测试等于拥有“老板视角”——能用数据说服团队优化方向、证明框架升级的收益,也能在面试与工作中成为最懂提速的那个人。

目录
  1. 可视化演示
  2. 基准测试设计
  3. JIT编译加速
  4. XLA优化原理
  5. FLOPS计算
  6. 内存分析
  7. 编译时间测量
  8. 优化前后对比
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示JAX基准测试流程(预热→执行→计时→统计→对比)、FLOPS计算与峰值利用率分析,以及JIT编译优化前后的性能对比。

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

基准测试五阶段流程 warm: 预热设备 run: 多次执行 stat: 统计分析 mean±σ 控制变量多次测量取统计值

基准测试设计

阿吉把一场“烘焙比武”的方案写在黑板上:旧炉子烤20炉碱水包,新炉子也烤20炉碱水包,面粉、室温、面团重量全部一致,连放烤盘的间距都量好。面包叔问:为什么不能随便烤两炉就下结论?阿吉说,烤炉会受天气、电压波动影响,今天快不代表明天快,必须用“同一条件、多次重复、取平均值”来消除偶然因素。

在JAX性能基准测试里,这套思路被称作基准测试设计。很多新手上来就写一个计时器包住训练循环,然后说“优化有效”,但结果往往站不住脚。正确的设计要先明确测试目标:你是想测单次推理延迟、训练吞吐,还是显存峰值?然后固定运行环境:GPU型号、驱动版本、JAX版本、batch size、输入shape,每一项都要锁死。接着要做重复实验,通常运行10到30次取均值,因为设备频率、缓存状态、后台进程都会造成抖动。最后设置对照组,比如“Eager模式 vs JIT模式”或“FP32 vs 量化版本”,只有对照组才有资格说“快了”。

面包叔盯着黑板点点头:原来测烤炉和测模型是同一个道理——公平、重复、对照组,少了哪一样,结论都像没有称重就报出面粉用量一样不可信。

Tavg=1N∑i=1Nti\displaystyle T_{\text{avg}} = \frac{1}{N} \sum_{i=1}^{N} t_i
这个公式是把多次运行耗时加起来除以次数,得到稳定平均耗时,消除单次运行的偶然波动。
  1. 记录每次运行耗时调用基准函数 N 次,记录每一次的耗时 t₁, t₂, …, tₙ,形成时间序列。
    ∑i=1Nti\sum_{i=1}^{N} t_i
  2. 除以运行次数取平均把总耗时除以 N,得到平均耗时,代表“典型表现”。
    Tavg=1N∑i=1NtiT_{\text{avg}} = \frac{1}{N} \sum_{i=1}^{N} t_i

JIT编译加速

第二天,阿吉用智能烤炉做第一炉面包时,光是设定程序就花了五分钟,出炉时间反而比旧烤炉还慢。面包叔急了:这新炉子是不是智商税?阿吉解释:第一炉之所以慢,是因为它在“编译”——读食谱、规划火候、记住流程;从第二炉开始,它直接按记忆执行,快得惊人。

这正是JAX中JIT编译的运作方式。JIT全称Just-In-Time,意思是“用的时候再编译”。当你第一次调用一个被jax.jit修饰的函数时,JAX会追踪Python函数的执行过程,把它转成一张静态计算图,然后交给XLA编译成GPU高效代码。编译产物会按参数形状和数据类型缓存下来;后续如果调用相同shape的参数,JAX直接命中缓存,跳过编译。因此,JIT的收益要从第二次调用开始算起。

面包叔恍然大悟:原来判断新炉子厉不厉害,得等它“记住菜谱”以后再看,不能拿第一炉的狼狈当结论。基准测试里,这一步叫“预热”。

S=TeagerTjit\displaystyle S = \frac{T_{\text{eager}}}{T_{\text{jit}}}
JIT加速比等于“不使用JIT的平均耗时”除以“使用JIT且预热后的平均耗时”,反映编译缓存带来的真实收益。
  1. 测量 Eager 平均耗时在普通函数上重复运行取平均,得到无优化基线。
    Teager=1N∑i=1NtieagerT_{\text{eager}} = \frac{1}{N} \sum_{i=1}^{N} t_i^{\text{eager}}
  2. 测量 JIT 稳态耗时先调用一次完成编译,再重复运行取平均,得到稳定执行时间。
    Tjit=1N∑i=1NtijitT_{\text{jit}} = \frac{1}{N} \sum_{i=1}^{N} t_i^{\text{jit}}
  3. 两者相除得到加速比比值小于1说明JIT反而变慢,大于1说明确实加速。
    S=TeagerTjitS = \frac{T_{\text{eager}}}{T_{\text{jit}}}

XLA优化原理

阿吉进一步向面包叔解释“智能烤炉”的内部设计:普通做法是“先倒面粉、再加水、再搅拌、再烘烤”,每一步都占用一个容器;智能炉则是把所有环节融合成“一键操作”,面粉和水直接进同一个搅拌烤盘,中间不需要把半成品倒来倒去。面包叔感叹:省掉的不是动作,是“搬运”。

这个比喻对应XLA优化原理中的算子融合。JAX通过jax.jit把计算图交给XLA编译器后,XLA不会按Python代码逐条执行,而是会把多个算子(如矩阵乘法、加法、激活函数)融合成一个kernel。原本中间结果要写入显存再读出来,融合后这些数据直接留在寄存器或片上缓存里,大幅减少了显存读写和kernel启动开销。除此之外,XLA还会做内存规划、公共子表达式消除和并行调度,让整个计算流程更像一条自动化流水线。

面包叔看着烤炉感叹:原来快不只是因为炉温高,更是因为它把“倒腾容器”的功夫省掉了。XLA的算子融合,就是用“少搬运”换“快出炉”。

Tfused≈Top1+Top2−Tio\displaystyle T_{\text{fused}} \approx T_{\text{op1}} + T_{\text{op2}} - T_{\text{io}}
融合后的耗时约等于两个算子单独执行的耗时之和,再减去中间结果写回显存再读入的时间。
  1. 算子分开执行的耗时算子1和算子2各自是独立kernel,总时间为两者之和。
    Tsep=Top1+Top2T_{\text{sep}} = T_{\text{op1}} + T_{\text{op2}}
  2. 中间结果的数据搬运开销两次kernel之间,中间张量需要写回显存再读入,引入额外IO时间。
    Tsep=Top1+Top2+TioT_{\text{sep}} = T_{\text{op1}} + T_{\text{op2}} + T_{\text{io}}
  3. 融合后省去搬运合并成一个kernel后,中间结果不再落地,总耗时减少。
    Tfused=Top1+Top2−TioT_{\text{fused}} = T_{\text{op1}} + T_{\text{op2}} - T_{\text{io}}

FLOPS计算

面包叔想知道新烤炉到底有多大产能,阿吉指着说明书说:理论产能是“每小时200个面包”,但实际能不能达到,得看一小时内真正烤了几个。如果烤箱频繁开关门、面团供应不上,理论数字就只是纸面功夫。

在JAX性能基准测试中,理论产能对应“理论FLOPS峰值”,即硬件每秒最多能执行的浮点运算次数;实际产能对应“实际FLOPS”,即你跑真实模型时每秒真正完成的浮点运算次数。两者之比叫计算利用率,通常在30%到70%之间。计算FLOPS的难点在于估算浮点运算次数:以一次线性层为例,前向计算量大约是“2 × 输出维度 × 输入维度 × batch size”,反向传播约为前向的两倍。统计出总浮点次数后,除以单次step的耗时,就得到了实际吞吐。

面包叔听完说:所以光看说明书没意义,得掐表数“实际出炉数”。AI性能优化也一样,理论FLOPS是广告,实际FLOPS才是实力。

FLOPS实际=N浮点运算次数T秒\displaystyle \text{FLOPS}_{\text{实际}} = \frac{N_{\text{浮点运算次数}}}{T_{\text{秒}}}
实际FLOPS衡量模型每秒真正完成了多少次浮点运算;把它和硬件理论峰值对比,就能知道算力被利用了几成。
  1. 估算浮点运算总次数前向传播按“2 × 参数量 × batch”估算,反向约为其两倍,汇总得到N。
    Nflop≈2×参数量×batch×3N_{\text{flop}} \approx 2 \times \text{参数量} \times \text{batch} \times 3
  2. 测量单步耗时在预热后用计时器记录一次前向加反向的耗时,单位秒。
    T=tend−tstartT = t_{\text{end}} - t_{\text{start}}
  3. 两者相除得到吞吐每秒完成的浮点运算次数,就是实际FLOPS。
    FLOPS实际=NflopT\text{FLOPS}_{\text{实际}} = \frac{N_{\text{flop}}}{T}

内存分析

面包叔的厨房不大,台面堆满了烤盘。阿吉每次烤完一批,都要看看台面上最多同时放了多少个烤盘——如果最高水位超过台面承受极限,就会“挤爆”厨房。面包叔这才明白:显存分析的本质,就是盯住“盘子最多”的那个时刻。

在JAX中,内存分析同样关注峰值显存。训练一个模型时,中间激活值、梯度、优化器状态都会占用设备显存。如果峰值超过GPU显存容量,就会出现OOM(Out Of Memory)报错。你可以用JAX的profiler或device_memory_profile()记录显存随时间的变化曲线,找到峰值出现的阶段,再针对性地优化:减少batch、使用gradient checkpoint、显式释放中间变量或调整计算顺序。值得注意的是,JAX的内存分配器会缓存和复用显存块,所以“峰值”并不等于“总是占用这么多”,分析时要结合分配器的复用行为。

面包叔在厨房里贴了一张“台面水位表”,每次打面团都记一笔。他感慨地说:显存管理跟厨房管理一样,最重要的不是平均放了几个盘,而是“最高峰时会不会乱成一锅粥”。

Mpeak=max⁡t∈[0,T]M(t)\displaystyle M_{\text{peak}} = \max_{t \in [0,T]} M(t)
峰值显存等于整个训练过程中任意时刻已分配显存的最大值,是判断是否OOM的关键指标。
  1. 记录每个时刻的已分配显存在时间轴上采样,得到函数M(t)。
    M(t)=M已分配(t)−M已释放(t)M(t) = M_{\text{已分配}}(t) - M_{\text{已释放}}(t)
  2. 对所有时刻取最大值找出显存水位的最高点。
    Mpeak=max⁡t∈[0,T]M(t)M_{\text{peak}} = \max_{t \in [0,T]} M(t)

编译时间测量

阿吉告诉面包叔:想真正理解新烤炉的效率,得把时间拆开看——“预热需要5分钟,烘焙需要10分钟,总时长15分钟”。如果只报告总时长,外人会以为烤面包就是15分钟,而不知道瓶颈在预热上。

在JAX里,一次jit函数的完整调用时间也是两段式:编译时间和执行时间。编译时间包括Python函数追踪、HLO图生成、XLA优化和底层kernel编译,通常只在首次调用时出现。执行时间则是编译完成后纯计算的耗时。测量方法很清晰:第一次调用前开始计时,得到“编译+执行”的总时间;预热后再次计时,得到纯执行时间;两者相减就是编译时间。另外,由于JAX默认异步派发,计时时一定要调用block_until_ready(),否则测到的是“提交任务”的时间,而不是“计算完成”的时间。

面包叔在值班表上分别记下“预热”和“烘焙”两栏时间,感叹道:不拆开看永远不知道时间都去哪了。优化AI模型也一样,把编译和执行分开计时,才能精准找到那根最长的木桶板。

Ttotal=Tcompile+Texecute\displaystyle T_{\text{total}} = T_{\text{compile}} + T_{\text{execute}}
端到端总耗时等于编译耗时加执行耗时;真正反映推理体验的是这个总和,而找瓶颈则需要把两者分开。
  1. 测量编译耗时从首次调用开始计时到编译完成,得到T_compile。
    Tcompile=T首次调用返回前−T任务提交T_{\text{compile}} = T_{\text{首次调用返回前}} - T_{\text{任务提交}}
  2. 测量执行耗时预热后重复调用并等待计算结果,得到纯执行时间。
    Texecute=1N∑i=1NtiexecT_{\text{execute}} = \frac{1}{N} \sum_{i=1}^{N} t_i^{\text{exec}}
  3. 相加得到端到端时间面向真实场景的报告应采用这个总和。
    Ttotal=Tcompile+TexecuteT_{\text{total}} = T_{\text{compile}} + T_{\text{execute}}

优化前后对比

最后一天,阿吉把改造方案汇总成一张对比表:旧烤炉20炉平均耗时300分钟,新烤炉200分钟;旧方案浪费面粉多,新方案省料;旧炉子的厨房台面高峰期堆12个盘,新炉子只堆7个。面包叔一看就笑了:这不就是“优化前后对比”吗?所有决策一目了然。

在JAX性能基准测试中,优化前后对比是给优化工作“盖棺定论”的关键一步。首先要在优化前记录基线数据:平均延迟、吞吐、峰值显存、端到端时间。然后应用优化(如开启JIT、调整XLA配置、量化),在完全相同的环境和输入下重新测量。接着计算加速比,即优化前耗时除以优化后耗时。除了延迟,还应关注内存下降比例和精度变化——量化加速往往伴随轻微的精度损失,只有综合评估才能判断优化是否值得。

面包叔把对比表贴在收银台后面,对每位顾客说:好烤炉不是靠嘴吹出来的,是靠计时器一炉一炉考出来的。AI优化同理——加速比,才是硬道理。

Speedup=TbeforeTafter\displaystyle \text{Speedup} = \frac{T_{\text{before}}}{T_{\text{after}}}
加速比表示优化后速度提升的倍数;它是性能报告中最直观、最核心的一个数字。
  1. 测量优化前平均耗时在基线环境上运行N次取平均,得到T_before。
    Tbefore=1N∑i=1NtibeforeT_{\text{before}} = \frac{1}{N} \sum_{i=1}^{N} t_i^{\text{before}}
  2. 测量优化后平均耗时在同样环境下启用优化并预热后重复测量。
    Tafter=1N∑i=1NtiafterT_{\text{after}} = \frac{1}{N} \sum_{i=1}^{N} t_i^{\text{after}}
  3. 相除得到加速比大于1表示优化生效,越大越快。
    Speedup=TbeforeTafter\text{Speedup} = \frac{T_{\text{before}}}{T_{\text{after}}}

AI 应用场景

训练性能优化与量化加速验证

阶段:模型训练性能优化

解决的问题:传统做法只看训练损失曲线的下降趋势,没人关心每一步耗时多少、显存还剩多少。一个模型训练到第100步才被发现batch过大、频繁OOM,浪费大量算力。用JAX基准测试,可以在训练前先对小规模模型做FLOPS、峰值显存和端到端计时,量化JIT和XLA优化到底带来多少收益,从而在投入大规模训练前锁定最优配置。

具体案例:某短视频推荐团队的召回双塔模型在JAX中实现,原始FP32训练一个step耗时3.2毫秒,峰值显存8.1GB;通过开启JIT并使用量化训练后,step耗时降到2.0毫秒,峰值显存降到5.6GB,加速1.6倍,显存下降31%。团队用基准报告说服业务方接受量化方案,训练成本明显下降。

推理服务上线前的性能验收

阶段:模型部署与推理

解决的问题:推理服务上线前必须承诺SLA(服务等级协议),比如“p99延迟低于50毫秒”。传统做法是上线后靠监控发现问题,风险很高。用JAX基准测试可以在离线环境模拟真实请求,测量预热后的延迟分布、吞吐量和显存水位,提前发现性能瓶颈。

具体案例:某LLM推理服务上线前采用JAX的JIT编译和XLA优化,基准测试脚本显示生成阶段的p50延迟从23毫秒降到14毫秒,p99从61毫秒降到38毫秒,端到端总吞吐提升1.8倍。运团队据此签署了SLA承诺,上线后表现稳定。

Python 演示

python42 行
import time import jaximport jax.numpy as jnp  # 一个简单的三层线性模型,用来对比 Eager 与 JITdef model(x, params):    for w, b in params:        x = jnp.dot(x, w) + b        x = jax.nn.relu(x)    return x  # 固定输入和参数,保证测量的是设备计算而非数据生成x = jnp.ones((256, 128))params = [(jnp.ones((128, 128)), jnp.zeros((128,))) for _ in range(3)] eager_model = modeljit_model = jax.jit(model) # 第一次调用触发编译,这一步不计入正式测量_ = jit_model(x, params)  def bench(fn, runs=30):    times = []    for _ in range(runs):        start = time.perf_counter()        out = fn(x, params)        out.block_until_ready()        end = time.perf_counter()        times.append(end - start)    return sum(times) / len(times)  eager_avg = bench(eager_model)jit_avg = bench(jit_model) print("Eager 平均耗时: {:.4f} ms".format(eager_avg * 1000))print("JIT 平均耗时:   {:.4f} ms".format(jit_avg * 1000))print("加速比: {:.2f}x".format(eager_avg / jit_avg))
目的:通过固定输入和参数,分别测量同一个模型在Eager模式和JIT模式下的平均耗时,计算加速比,验证JIT编译和XLA优化的实际收益。
  1. jit_model = jax.jit(model)把普通函数包装成JIT版本,触发JAX的编译优化。
  2. _ = jit_model(x, params)预热,让首次编译先完成,避免把编译时间混入后续测量。
  3. out.block_until_ready()等待异步计算真正完成,否则计时器测到的只是任务提交时间。
  4. sum(times) / len(times)多次运行取平均,消除设备频率抖动和后台进程干扰。

常见误区

误区第一次调用JIT函数时慢得离谱,比普通函数还慢,于是得出结论:JAX的JIT就是个噱头,还不如不用。

正解第一次调用慢是因为JIT在背后做编译——就像智能烤炉第一炉要先设程序。编译是一次性成本,编译产物会被缓存;预热后重复调用会显著快于Eager模式。基准测试必须先预热,再对比稳态执行时间,这才是JIT的真实表现。

为何常见:直觉上人们把“第一次看到的耗时”当成函数的固有性能,忽略了编译阶段的存在。

误区看到硬件理论FLOPS有几十甚至上百T,就以为任何模型跑上去都会飞快。

正解理论FLOPS是硬件峰值,实际FLOPS受内存带宽、数据搬运、算子融合程度和并行效率制约。真正该看的是计算利用率,也就是实际FLOPS与理论峰值的比值,以及端到端延迟。

为何常见:厂商宣传页上印的全是理论峰值,铺天盖地的营销让小白误把“天花板”当“地板”。

误区显存峰值越低就代表内存优化越好,只要峰值不超过显存容量就万事大吉。

正解JAX的分配器会预分配并复用显存块,峰值只代表某个瞬时的占用水位,不代表频繁分配的开销。真正的优化还要看分配次数、内存复用好坏、碎片化程度,以及峰值出现在哪个阶段。

为何常见:峰值是一个简单醒目的数字,人天生容易被一个数字说服,却忽略了背后的分配机制。

误区优化改完了,跑一次看速度提升了,立刻开始写报告、发喜报。“结果已经很明显了啊,为什么还要跑20次?”

正解设备频率波动、缓存冷热、后台进程都会让单次测量出现20%甚至更大的误差。正确做法是固定环境、多跑几轮取平均或中位数,并记录方差,才能判断加速效果是真实的还是噪音。

为何常见:重复测量麻烦且耗时,人们潜意识里想尽快结束枯燥的测试环节。

误区只要整个程序跑下来时间变小了,优化就算成功,不需要区分哪段时间是编译、哪段是执行。

正解端到端时间是编译加执行加数据搬移的总和。如果不拆分,就无法定位瓶颈:可能是编译慢了,也可能是执行慢了,甚至可能是数据加载拖了后腿。只有分开计时,才能找到优化的下一个发力点。

为何常见:整体包一层计时很简单,拆分测量需要额外设计和代码,懒惰让人选择了最省事的方案。

面试题

Q1. 在JAX基准测试中,为什么第一次调用JIT函数往往比后续调用慢?

单选题 · easy

  • A.JIT首次需要编译缓存← 正确答案
  • B.GPU首次启动一定慢
  • C.CPU缓存加载的过程
  • D.首次调用发生内存泄漏

答案:A。JIT首次调用需要完成Python函数追踪、计算图构建和XLA编译,并缓存编译产物,后续相同shape的调用直接命中缓存。就像智能烤炉第一炉要翻说明书调试,之后的炉都走肌肉记忆。GPU首次启动慢虽可能发生,但不是JIT慢的根本原因。

Q2. 在端到端计时中,T_total = T_compile + T_execute,正式性能报告应如何处理T_compile?

单选题 · mid

  • A.剔除编译只测稳态执行← 正确答案
  • B.每次运行都包含编译耗
  • C.编译时间只对GPU有意义
  • D.编译时间等于执行延迟

答案:A。正式性能报告通常关注稳态吞吐,应先预热剔除编译开销;若面向真实首次体验,则需报告包含编译的端到端时间。编译时间不只影响GPU,CPU上同样存在。每次运行都包含编译会严重高估真实耗时。

Q3. 下列哪些属于合理的性能基准测试设计?

多选题 · mid

  • A.固定批次大小和输入形状← 正确答案
  • B.多次重复求取平均耗时← 正确答案
  • C.相同环境对比优化前后← 正确答案
  • D.每次运行都更换输入形状

答案:ABC。固定batch和输入shape才能让编译缓存和计算量保持一致;重复取平均能消除抖动;相同环境是对照实验的前提。每次更换输入shape会不断触发重新编译,测出的不是稳定性能。

Q4. 在JAX中考察“实际FLOPS”时,实际吞吐与理论峰值之比叫什么?

单选题 · easy

  • A.实际吞吐与理论峰值之比← 正确答案
  • B.内存复用与分配总量之比
  • C.编译缓存命中与全部之比
  • D.网络传输与计算时间之比

答案:A。实际FLOPS除以理论FLOPS得到计算利用率,反映硬件算力被有效利用的程度。内存复用率、编译缓存命中率是其他层面的指标,与计算利用率不是同一概念。

Q5. XLA算子融合主要消除了哪部分开销?

单选题 · mid

  • A.CPU和GPU之间通信开销
  • B.损失函数内部的调试开销
  • C.中间结果的显存读写开销← 正确答案
  • D.Python解释器的自动求导开销

答案:C。XLA把多个算子融合成一个kernel,中间结果不再写回显存再读入,主要省去的是全局内存的IO开销。CPU与GPU通信、Python解释器开销不是算子融合要解决的核心问题。

Q6. 用JAX做端到端基准时,以下哪些因素会造成测量偏差?

多选题 · hard

  • A.未预热导致GPU频率偏低← 正确答案
  • B.固定随机种子保持不变
  • C.未调用设备同步等待← 正确答案
  • D.使用自动混合精度训练

答案:AC。未预热时GPU可能处于低频节能状态,测得结果偏低;未调用block_until_ready()则测到的是异步提交时间而非计算完成时间。固定随机种子是正确做法,使用混合精度是有意配置而非偏差。

Q7. 关于JAX的设备内存分配器,下列哪项是正确的?

单选题 · mid

  • A.每个算子独占全新显存
  • B.分配器复用释放的显存← 正确答案
  • C.峰值显存等于理论需求
  • D.显存由垃圾回收器管理

答案:B。JAX的内存分配器会缓存已释放的显存块并复用于后续张量,减少重复分配开销。因此峰值显存不等于理论需求,每个算子也并非独占全新显存。

Q8. 下列哪个是“加速比”的正确计算公式?

单选题 · easy

  • A.优化前耗时除以优化后← 正确答案
  • B.优化后耗时除以优化前
  • C.运行总次数除以总耗时
  • D.每秒浮点运算除以参数

答案:A。加速比的定义是“优化前耗时 ÷ 优化后耗时”,大于1表示优化生效。其余公式分别是吞吐和单位参数运算量的定义,不是加速比。

Q9. 在对比量化前后的模型性能时,除了计算吞吐还应关注哪些指标?

多选题 · hard

  • A.端到端的推理延迟变化← 正确答案
  • B.模型精度或质量退化← 正确答案
  • C.峰值内存与显存占用← 正确答案
  • D.数学公式的书写美观

答案:ABC。量化会同时影响延迟、内存和精度,只有综合评估才能判断是否值得。公式美观与模型性能毫无关系,属于干扰项。

Q10. 关于JIT编译的首次开销,在生产环境中最合理的做法是什么?

单选题 · extreme

  • A.结合应用场景决定预编译← 正确答案
  • B.因为首次太慢而放弃它
  • C.因为后续较快而盲目用
  • D.直接增大批处理量再测

答案:A。如果服务对延迟非常敏感,应在启动时预编译预热;如果是一次性计算,加入编译开销也无妨。放弃JIT或盲目使用都不理智,增大batch不能解决编译开销问题。