设想你是一家深夜食堂的老板,后厨只有一位大厨、一口炒锅。顾客接连推门进来:刚下夜班的护士只想赶紧打包一碗热汤,加班到现在的程序员饿得前胸贴后背,还有一位老主顾打算慢悠悠坐到打烊。如果老板老实按进门顺序做菜,护士可能等四十分钟也吃不上;可如果永远给最着急的人插队,那位老主顾恐怕要饿着肚子看完全程。怎样才能让“急的人吃得上、慢的人不掀桌”?这正是每家推理服务器每天都在做的难题。 ONNX Runtime 就是这家“深夜食堂”的后厨系统。推门进来的“顾客”,是来自不同业务方的推理请求:门禁要做人脸识别、App 要做商品推荐、语音助手要听懂你刚才那句话。它们对延迟的忍耐力天差地别——门禁 30 毫秒不响应就要报警,推荐慢一秒用户毫无察觉。推理请求优先级调度,就是大厨手里那本“做菜顺序手册”:谁先下锅、谁在排队、什么时候允许插队、又怎么防止某些请求永远等不到灶台。这篇文章,就带你把手册一页页翻开。

岗位关联

学完本文,你将理解 ONNX Runtime 中的优先级调度设计,能在部署中让高价值请求更及时、算力分配更高效。

目录
  1. 可视化演示
  2. 优先级基础
  3. 队列管理机制
  4. 核心调度算法
  5. 线程池协同
  6. 调度实现细节
  7. 性能权衡分析
  8. 工程优化实践
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示推理请求优先级调度:请求按优先级分数排序入队、高优先级请求调度GPU,静态/动态/自适应三种调度策略对比,及GPU抢占与SLA分层保障。

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

优先级队列调度 请求按优先级分数排序入队 高优先级请求优先调度GPU执行 score=f(租户,SLA,时间) 优先级评估→队列管理→资源调度三阶段。

优先级基础

老板给顾客发三色预约卡:红卡是急诊护士、黄卡是普通上班族、绿卡是闲坐老主顾。卡片颜色只说明一件事——你有多急。优先级,就是推理世界里那张“预约卡”的颜色。

在推理服务中,每个请求同样带着“颜色”进入系统。业务方在发起推理时声明优先级,例如 3 表示最高、1 表示最低。这个数值会跟请求一路同行,直到调度器决定“下一个该谁下锅”。优先级分为静态与动态两类:静态优先级由业务方声明后基本不变;动态优先级会根据请求的等待时间、剩余截止时间实时调整。有了优先级,接下来要会分类——在线请求(人脸识别、搜索)属于延迟敏感型,离线请求(批量报表、数据清洗)属于吞吐型,两者对延迟的忍耐截然不同。而“服务质量”(QoS/SLA)就是把这种忍耐量化,例如承诺 P99 延迟低于 50 毫秒。调度目标因此清晰起来:在满足高优先级 SLA 的前提下,尽可能提高整体吞吐与资源利用率。

于是大厨手里的卡不再只有三种颜色,而是一张张“综合得分”不断变化的记分牌——颜色决定底分,距离打烊的时间决定加分。这正是真实调度器计算优先级的朴素雏形。

Peff=w1×Pbase+w2×1D−tnowP_{\text{eff}} = w_1 \times P_{\text{base}} + w_2 \times \frac{1}{D - t_{\text{now}}}
综合优先级 = 基础重要程度 × 权重 + 紧迫程度 × 权重;越接近截止时间,请求的“急”分越高。
  1. 基础优先级 $P_{\text{base}}$业务方预先声明的重要程度,对应“红黄绿卡”的基础颜色。
    Pbase∈{1,2,3}P_{\text{base}} \in \{1, 2, 3\}
  2. 截止时间 $D$请求必须在某个时刻前得到响应,例如门禁场景的 30ms 预算。
    D=tarrive+TdeadlineD = t_{\text{arrive}} + T_{\text{deadline}}
  3. 剩余窗口 $D - t_{\text{now}}$距离截止还有多久,越小越危急,取倒数后数值飙升。
    Δ=D−tnow\Delta = D - t_{\text{now}}
  4. 权重系数 $w_1, w_2$系统调节“身份”与“紧迫度”各自的分量。
    w1+w2=1,w1,w2≥0w_1 + w_2 = 1, \quad w_1, w_2 \ge 0

队列管理机制

红黄绿卡发出去之后,订单要有个地方放。食堂用的是带夹子的订单夹:新订单从右侧夹入,先到的夹在前面;夹子数量有限,夹太满大厨反而手忙脚乱。

推理服务器里的“订单夹”就是请求队列。最简单的队列入队出队遵循先进先出(FIFO),但有了优先级后,队列通常被拆成多条:每个优先级对应一条独立队列,调度器先看红卡队列再看黄卡队列。这一步就把“谁先下锅”变成了工程结构。在 ONNX Runtime 生态中,请求级队列通常由上层推理服务(如 Triton、自研网关)管理,运行时层则以任务为单位调度算子。多级队列的好处是隔离:某个低优先级队列即使爆满,也不会堵住高优先级队列。队列还要配两个“安全阀”:一是 TTL 超时机制,请求在队列里待得太久直接作废或降级,避免无意义等待;二是容量上限,当队列接近满载时触发背压(backpressure),拒绝或降级新请求,保护已入队的请求不至于被拖死。

老板有一条铁律:宁可对刚进门的客人说“今晚满座”,也不能让 20 张订单全堆在夹子上最后全部凉掉。有界队列加背压,正是食堂能在深夜高峰稳住出餐节奏的根本。

pdrop=max⁡(0,Q−QsoftQmax⁡−Qsoft)p_{\text{drop}} = \max\left(0, \frac{Q - Q_{\text{soft}}}{Q_{\max} - Q_{\text{soft}}}\right)
队列长度超过软上限后,丢弃新请求的概率从 0 平滑升到 1:先温和劝退,再全量拒绝。
  1. 当前队列长度 $Q$此刻订单夹上夹着的订单总数。
    Q=∑ilen(queuei)Q = \sum_{i} \text{len}(queue_i)
  2. 软上限 $Q_{\text{soft}}$超过这条线,新订单开始有被劝退的风险。
    Qsoft=0.8×Qmax⁡Q_{\text{soft}} = 0.8 \times Q_{\max}
  3. 硬上限 $Q_{\max}$订单夹彻底满员,新订单直接拒收。
    Q≤Qmax⁡Q \le Q_{\max}
  4. 丢弃概率 $p_{\text{drop}}$分子与分母的比例决定“劝退力度”。
    0≤pdrop≤10 \le p_{\text{drop}} \le 1

核心调度算法

三位大厨三种流派:正经大厨死守“先来后到”;性急大厨专挑红卡先炒;圆滑大厨给红卡 3 个灶台时间、黄卡 2 个、绿卡 1 个轮着来。

这些流派对应调度算法中的 FCFS(先来先服务)、优先级抢占和加权轮转,而现代系统更常用多级反馈队列,让请求在队列之间升降级。算法解决的核心问题是:当多个请求同时等着,到底依据什么决定下一个?先来先服务最公平也最简单,但一个漫长的低优先级请求堵住时,后面所有红卡都要干等——这在实时推理里不可接受。优先级抢占相当于把红卡插到队首,但注意这里的“抢占”发生在请求尚未开始执行之前,真正跑起来的推理任务通常不被打断,否则 GPU 上下文切换代价极高。加权轮转(WRR)给每个优先级一个权重,按权重比例轮流服务,既让红卡拿到更多时间,也保证黄卡绿卡有机会上桌。多级反馈队列(MLFQ)更进一步:请求按优先级进入不同层级的队列,长时间得不到服务的请求自动升级,系统不再“一卡定终身”。

圆滑大厨的“轮着来”,本质是给每个颜色一条活路——红卡多吃、黄卡够吃、绿卡有得吃,没人被饿到掀桌。

Si=wi∑j=1Kwj×CS_i = \frac{w_i}{\sum_{j=1}^{K} w_j} \times C
每个优先级在一个轮转周期内分到的服务时间,等于它的权重占所有权重之和的比例,再乘上周期总时间片。
  1. 权重 $w_i$第 i 个优先级的分量,红卡像点了 3 道菜,绿卡只点 1 道。
    w红=3,w绿=1w_{\text{红}} = 3, \quad w_{\text{绿}} = 1
  2. 权重总和 $\sum_{j=1}^{K} w_j$把所有优先级的权重加起来做归一化分母。
    ∑j=1Kwj=w1+w2+⋯+wK\sum_{j=1}^{K} w_j = w_1 + w_2 + \cdots + w_K
  3. 周期服务量 $C$一个完整调度周期包含的总时间片,类似一晚上可用的灶台总时长。
    C=∑i=1KSiC = \sum_{i=1}^{K} S_i
  4. 份额 $S_i$算出每个优先级“理应”得到的服务时间比例。
    ∑i=1KSi=C\sum_{i=1}^{K} S_i = C

线程池协同

食堂生意爆了,老板咬牙请来四位大厨、四个灶台。但问题来了:菜是先切好还是到灶台前再切?谁负责哪口锅?四位大厨会不会抢同一把菜刀?

推理服务器同样“请大厨”——线程池。ONNX Runtime 为每个 CPU 核心配置 worker 线程,每个线程像一个灶台。调度器把“下一道菜”(任务)交给空闲灶台,线程池则决定同时能开几个火。先说架构:线程池通常绑定核心数,每个 worker 从共享任务队列取任务执行;算子图(模型执行计划)被拆成多个可并行算子,这就是任务分解。并行度控制是双刃剑:线程数太少浪费算力,太多则上下文切换开销反噬性能,经验上以核心数为基准微调。负载均衡靠“任务窃取”(work stealing):空闲线程发现自己的队列空了,就去偷别人的任务,避免忙闲不均。优先级调度在这里的落点是:高优先级请求的任务进入共享队列更靠前的位置,或者由专用线程优先处理。

四位大厨绑定了四口锅,但洗菜、切配、装盘还是同一条流水线——这就是为什么线程池加了又加,单请求延迟却很快触底:串行瓶颈永远在那儿。

S=1(1−f)+fNS = \frac{1}{(1 - f) + \dfrac{f}{N}}
Amdahl 定律:加速比的上限由串行部分决定,加再多的灶台也突破不了串行瓶颈。
  1. 可并行占比 $f$推理中可以并行执行的算子所占比例。
    f∈(0,1)f \in (0, 1)
  2. 串行部分 $1 - f$必须逐个完成的数据依赖,比如分块聚合。
    1−f=不可并行比例1 - f = \text{不可并行比例}
  3. 线程数 $N$灶台(worker 线程)个数。
    N≤Ncpu coresN \le N_{\text{cpu cores}}
  4. 加速比上限当 $N \to \infty$ 时,$S$ 趋向于串行比例的倒数。
    lim⁡N→∞S=11−f\lim_{N \to \infty} S = \frac{1}{1 - f}

调度实现细节

插队多了,矛盾也来了。绿卡客人占着唯一一张大圆桌不走,红卡护士只能干等——这叫优先级反转;而一位绿卡老主顾等了一整晚连口汤都没喝上,摔门而去——这叫饥饿。

工程实现里,调度器不是简单地“按颜色排队”,还得处理资源竞争和公平性这两种“纠纷”。先看接口:ONNX Runtime 本身暴露线程数与执行模式的配置项,请求级优先级通常由上层网关读取业务字段(如 HTTP header),打上优先级标签后再送入队列。优先级反转是经典陷阱:低优先级任务先拿到了一把锁,高优先级任务等这把锁,结果中等优先级任务趁机抢占 CPU,低优先级永远轮不到——锁就一直释放不了。缓解方案是优先级继承(先到者临时继承高优先级,尽快释放锁)。饥饿防护则靠老化:等待越久,有效优先级越高,强制让低优先级请求浮出水面。最后一道防线是限流保护:令牌桶控制进入速率,超过阈值直接拒绝,防止雪崩。

大厨更新了手册:绿卡等够十分钟无条件升黄卡、二十分钟升红卡——被冷落的人有了盼头,插队引发的纠纷自然少了。

Peff=Pbase+⌊twaitTage⌋×ΔPP_{\text{eff}} = P_{\text{base}} + \left\lfloor \frac{t_{\text{wait}}}{T_{\text{age}}} \right\rfloor \times \Delta P
每等满一个老化周期,请求的有效优先级就涨一格,直到再也不至于被无限后推。
  1. 已等待时间 $t_{\text{wait}}$请求从入队起算的等待时长。
    twait=tnow−tenqueuet_{\text{wait}} = t_{\text{now}} - t_{\text{enqueue}}
  2. 老化周期 $T_{\text{age}}$多久“涨一格卡”,例如 500ms。
    Tage=500msT_{\text{age}} = 500\text{ms}
  3. 取整 $\lfloor \cdot \rfloor$等待不足一个周期不涨分,避免频繁抖动。
    ⌊twaitTage⌋=0,1,2,…\left\lfloor \frac{t_{\text{wait}}}{T_{\text{age}}} \right\rfloor = 0, 1, 2, \dots
  4. 增量 $\Delta P$每周期提升的优先级高度,通常取 1。
    ΔP=1\Delta P = 1

性能权衡分析

老板开始用指标管理食堂:翻台率(客人多久吃完走人)和好评率(客人吃不吃得满意)。一味追求翻台率,就让红黄绿全按“快菜”做,客人不满意;一味讨好,灶台又转不起来。

推理系统的两个核心指标是延迟与吞吐:优先级调度优先照顾高价值请求,必然牺牲部分低价值请求的吞吐;反过来,追求整体吞吐最大,又会拉高长尾延迟。性能权衡就是在这两端找平衡点。公平性通常用 Jain 公平指数量化,衡量所有请求获得资源是否均等。缓存亲和性则是巧劲:请求尽量命中同一 worker 线程的算子缓存、权重缓存,能显著降低延迟——这与“把熟客安排给同一服务员”异曲同工。成本评估要把 GPU 利用率、SLA 违约成本放在同一张表上算账:为 1% 的极端请求预留 30% 资源很可能不划算,不如允许其少量超时并赔付。

聪明的老板不会让所有顾客都吃上“米其林大餐”,而是让红卡吃好、黄卡吃饱、绿卡不饿,再用好评率和翻台率持续校准今天的排菜顺序。

J=(∑i=1nxi)2n∑i=1nxi2J = \frac{\left(\sum_{i=1}^{n} x_i\right)^2}{n \sum_{i=1}^{n} x_i^2}
Jain 公平指数:结果越接近 1,说明请求之间分到的资源越均匀;越接近 1/n,说明资源越被少数请求独占。
  1. 资源量 $x_i$第 i 个请求获得的吞吐或算力份额。
    xi≥0x_i \ge 0
  2. 总和平方把所有请求资源加起来再平方。
    (∑i=1nxi)2\left(\sum_{i=1}^{n} x_i\right)^2
  3. n 倍平方和分母用平方和放大“不均”的信号。
    n∑i=1nxi2n \sum_{i=1}^{n} x_i^2
  4. 取值区间全体均等时 $J=1$,一个请求独吞时 $J=1/n$。
    1n≤J≤1\frac{1}{n} \le J \le 1

工程优化实践

手册翻到了最后一章:实战预案。大厨把“红黄绿”规则细化成一张可调节的排班表,还装了个小铃铛——哪口锅压单了会响。

调度方案不是一锤子买卖。实际部署中,优先级、队列深度、老化周期、权重都要跟着业务流量和硬件负载反复调优。参数调优通常以 P99 延迟和吞吐为观察指标,用小步迭代完成。批处理融合是提升吞吐的王牌:把若干同优先级、同形状的短请求合并成一个大 batch 一次推理,GPU 利用率大幅上升,代价是单个请求要多等一会儿“拼车”。动态调整指根据实时负载修改权重:高峰期把高优先级权重调高,闲时调低来照顾低优先级积压。监控告警则是最后的安全网——队列深度、丢弃率、P99 延迟、饥饿计数都要可视化,超过阈值自动告警或在 K8s 上扩容。

最后大厨悟了:做菜顺序重要,但“一次多炒几份”和“随时看铃铛调整”同样重要。这本手册的终点,是把调度从艺术变成可度量的工程。

Tavg=Tbatch+ToverheadBT_{\text{avg}} = \frac{T_{\text{batch}} + T_{\text{overhead}}}{B}
把 B 个请求合并成一次推理:总时间摊到每个请求头上,batch 越大,单请求平均耗时越低——前提是合并开销远小于收益。
  1. 批次大小 $B$一次合并推理的请求数量。
    B=8,16,32,…B = 8, 16, 32, \dots
  2. batch 总耗时 $T_{\text{batch}}$整个批次在 GPU 上跑完的时间。
    Tbatch≈Tsingle+小幅度增长T_{\text{batch}} \approx T_{\text{single}} + \text{小幅度增长}
  3. 合并开销 $T_{\text{overhead}}$拼装张量、拆分结果的额外成本。
    Toverhead≪TbatchT_{\text{overhead}} \ll T_{\text{batch}}
  4. 平均耗时 $T_{\text{avg}}$摊到每个请求身上的“拼车价”,实际中随 B 增长总耗时也会上升,因此存在最优批次大小。

AI 应用场景

在线推荐系统的分级服务

阶段:推理服务部署与运行时调度

解决的问题:一个推荐集群同时服务首页信息流、搜索结果和广告竞价三类请求。传统单一 FIFO 队列下,信息流的大流量请求长时间占据 GPU,广告请求偶尔被挤到超时,直接影响广告收入。优先级调度把“食堂的'红黄绿卡'搬到线上”:在网关处为不同业务打上不同颜色,让高价值请求先下锅。

具体案例:某电商平台在网关处为广告请求打上最高优先级标签,搜索次之,信息流最低;同时为广告请求预留 20% 的推理线程,并开启批处理融合。上线后广告请求 P99 延迟从 200ms 降至 65ms,信息流 P99 仅增加 8ms,符合 SLA 预期。

自动驾驶多任务推理

阶段:端侧 AI Runtime 资源调度

解决的问题:自动驾驶域控制器同时运行目标检测、车道线识别、地图更新等模型,检测帧延迟直接关系行车安全,而地图更新对实时性要求低且数据量大。优先级调度的目标不再是“多赚钱”,而是“保安全”。

具体案例:某团队基于 ONNX Runtime 构建分层调度:检测帧以最高优先级进入线程池,地图更新任务降级并批量处理。通过优先级调度与批处理融合,检测端到端延迟稳定在 20ms 内,地图更新吞吐提升约 1.8 倍。

Serverless 推理平台多租户 QoS

阶段:云平台推理服务治理

解决的问题:多租户共享 GPU 的 Serverless 平台上,高付费租户与免费租户混跑,免费租户的突发流量会污染付费租户的服务质量。优先级调度成了“分碗筷”的策略——先保证付了钱的客人吃上热饭。

具体案例:平台在请求入口解析租户等级并打上优先级标签,低优先级离线任务只在 GPU 空闲窗口调度,同时用有界队列加背压保护核心租户,上线后租户 SLA 违约率下降 70%。

Python 演示

python56 行
"""ONNX Runtime 风格推理请求优先级调度模拟"""import heapqimport timefrom dataclasses import dataclass  @dataclassclass Request:    priority: int        # 数值越大优先级越高(3=高 2=中 1=低)    request_id: int      # 请求编号,同优先级时保证先来先服务    estimate_ms: float   # 单次推理预计耗时(毫秒)    arrived_at: float  class PriorityScheduler:    """基于堆实现的高优先级先出调度器"""     def __init__(self):        self._heap = []        # 元素格式: (-priority, seq, request)        self._seq = 0        self.served_count = 0     def submit(self, priority: int, estimate_ms: float) -> None:        req = Request(priority, self._seq, estimate_ms, time.time())        self._seq += 1        heapq.heappush(self._heap, (-req.priority, req.request_id, req))     def run_next(self) -> float:        """取出最高优先级请求执行,返回实际耗时(ms)"""        if not self._heap:            return 0.0        _, _, req = heapq.heappop(self._heap)        t0 = time.time()        time.sleep(req.estimate_ms / 1000.0)   # 模拟一次推理        self.served_count += 1        return (time.time() - t0) * 1000     def pending(self) -> int:        return len(self._heap)  def main():    scheduler = PriorityScheduler()    # 三个业务方:门禁(3 最高)、广告(2)、日志(1 最低)    scheduler.submit(3, 80)    scheduler.submit(1, 200)    scheduler.submit(2, 120)    scheduler.submit(3, 90)    while scheduler.pending() > 0:        cost = scheduler.run_next()        print(f"[完成第 {scheduler.served_count} 个] 耗时 {cost:.1f}ms, "              f"队列剩余 {scheduler.pending()} 个请求")  if __name__ == "__main__":    main()
目的:模拟推理请求进入队列后,调度器始终弹出“优先级最高”的请求先执行——就像大厨永远先照顾红卡客人。
  1. heapq.heappush(self._heap, (-req.priority, req.request_id, req))把优先级取负号再入堆,Python 的小顶堆就变成了“大顶堆”,最高优先级永远在堆顶。
  2. _, _, req = heapq.heappop(self._heap)弹出堆顶的三元组,前两个元素是排序用的,真正要执行的是第三个 req。
  3. time.sleep(req.estimate_ms / 1000.0)用睡眠模拟一次推理的耗时,让队列行为可观察。
  4. scheduler.submit(3, 80)门禁请求以最高优先级 3 提交,即使它排在后边,也会被堆顶弹出先执行。
  5. while scheduler.pending() > 0:调度主循环,等价于食堂里“只要还有订单夹就一直做菜”。

常见误区

误区我把请求设成最高优先级,它跑得肯定比别的请求快很多,因为系统会把它插到最前面,还会优先给它分配算力。

正解优先级决定的是“排队顺序”,不是“执行速度”的加成。调度器只是把一个请求从队尾挪到队首,一旦它真正开始推理,占用的计算资源和低优先级请求没有区别。就像食堂的“加急单”只是先下锅,火候不会因此变大。

为何常见:操作系统里“优先级高的进程更早拿到 CPU”给人“高优先级=更快”的直觉印象,但两者作用的阶段不同:一个管排队,一个管跑速。

误区既然优先级有用,那我把它服务的所有请求都标成最高优先级,大家不就都能享受最短延迟了吗?

正解当所有请求优先级相同时,调度器只能在它们之间做无差别选择,效果退化为先来先服务(FCFS),谁也“优先”不了。更糟的是,如果有一小撮请求被标成“非常高”,低优先级请求会被无限推后,出现饥饿——整体服务质量反而下降。

为何常见:缺少博弈视角。单个业务方从自身利益出发都想争抢最高优先级,却忽略了调度的相对排序本质。

误区高优先级请求一进来,正在跑的推理任务应该立刻让路,把我插进去先算。

正解大多数推理调度是非抢占式的:一旦一个请求对应的算子图开始在线程池里执行,就会一路跑到完成。所谓“抢占”通常发生在一个请求还没被分配线程的前排队阶段。真正的中断式抢占需要算子级支持,ONNX Runtime 默认不提供,代价也很高。

为何常见:操作系统的抢占式调度(时间片切换)深入人心,人们把“线程被踢下 CPU”的习惯平移到了推理请求上。

误区我把队列设得长一点,所有请求都先收进来排队,总比直接拒绝好。

正解无界队列有两个致命问题:一是请求延迟随队列深度线性增长,早到的请求可能超时;二是内存占用持续膨胀,最终拖垮整个服务。工程上使用有界队列 + TTL + 背压,队列接近容量时直接拒绝新请求或返回降级响应,保护已接收请求的延迟。

为何常见:直觉上“收下所有请求”比“拒绝请求”更友好,但实时系统里“及时拒绝”往往才是对整体服务质量更负责任的做法。

面试题

Q1. ONNX Runtime 推理请求的优先级主要影响什么?

单选题 · easy

  • A.推理计算本身的执行速度
  • B.请求在队列中的等待顺序← 正确答案
  • C.模型权重在内存中的存放位置
  • D.输入张量的数据类型格式

答案:B。优先级决定的是请求“排队多久”而不是“跑得多快”,对应食堂里先下锅但不改变火候。C、D 与优先级完全无关,A 是把排队顺序误当成执行速度加成。

Q2. 当所有请求都被设为最高优先级时,调度效果最接近以下哪种?

单选题 · easy

  • A.所有请求都明显提速
  • B.高优先级全部失效
  • C.服务退化为先来先服务← 正确答案
  • D.模型精度自动下降

答案:C。优先级是相对概念,大家都一样时谁也不能插队,系统只能按到达顺序服务,即退化为 FCFS。A、D 不会发生,B 表述不完整——不是“失效”,而是“不再有区分度”。

Q3. 队列背压的主要作用是什么?

单选题 · easy

  • A.队列接近容量时主动拒绝新请求← 正确答案
  • B.自动提升所有请求的优先级
  • C.加快模型单次推理计算速度
  • D.把请求转发给离线批处理队列

答案:A。背压是“订单夹满员时对客人说今晚满座”——在入口处拒绝或降级,保护已入队请求的延迟。B、C 与背压无关,D 是降级手段之一但不是背压的核心定义。

Q4. 优先级反转是指什么?

单选题 · mid

  • A.高优先级请求被低优先级请求阻塞← 正确答案
  • B.低优先级请求被高优先级请求抢占
  • C.所有请求的优先级数值发生反转
  • D.队列按照随机顺序排出请求

答案:A。低优先级任务持有共享锁,高优先级任务排队等锁,中间优先级又抢占 CPU,导致锁迟迟释放不了——这正是“绿卡占着大桌不让,红卡干着急”。B 是正常抢占,C、D 与定义不符。

Q5. 老化机制 Aging 主要解决的问题是?

单选题 · mid

  • A.解决低优先级请求长期饥饿← 正确答案
  • B.提升线程池的并行度水平
  • C.防止模型推理精度下降
  • D.提高 GPU 显存利用效率

答案:A。老化让等待越久的请求优先级越高,把被冷落的绿卡逐步升成红卡,从而防止饿死。B、C、D 与老化机制的作用对象完全无关。

Q6. 加权轮转调度中,权重最直接的作用是?

单选题 · mid

  • A.决定请求输出张量的精度
  • B.决定模型量化的位宽大小
  • C.决定各优先级服务时间比例← 正确答案
  • D.决定线程池的线程数量

答案:C。加权轮转里权重决定“红卡 3 道菜、绿卡 1 道菜”的服务时间比例,而非执行速度或精度。A、B 与调度无关,D 由并行度控制决定。

Q7. 非抢占式推理调度下,高优先级请求的端到端延迟主要由哪几部分组成?

单选题 · hard

  • A.仅等于当前任务的剩余执行时间
  • B.等于排队时间加上单次执行时间
  • C.等于等待当前任务加上排队时间
  • D.等于等待当前任务加排队加执行时间← 正确答案

答案:D。非抢占意味着正在执行的任务不会被打断,高优先级请求必须等它跑完,再加上自己在队列里的等待以及自身执行时间。A、B、C 都各漏了一部分,就像红卡客人不仅要等前面那道菜出锅,还要等自己的菜做完。

Q8. 哪些策略能有效缓解低优先级请求的饥饿问题?

多选题 · mid

  • A.老化机制随等待时间提升优先级← 正确答案
  • B.多级反馈队列动态调整请求等级← 正确答案
  • C.扩大高优先级独占资源比重
  • D.简单丢弃所有等待超时的请求

答案:AB。老化和多级反馈都是给低优先级“兜底升级”的机制,能有效消除饥饿。C 会加剧低优先级被饿死,D 的丢弃只是止损而非“让请求吃上饭”。

Q9. 关于推理线程池与调度,下列说法正确的有?

多选题 · hard

  • A.线程池并行度决定同时执行的推理任务数← 正确答案
  • B.调度器主要决定任务进入线程池的执行顺序← 正确答案
  • C.线程数越多单个请求延迟必然越低
  • D.任务窃取可缓解线程间负载不均← 正确答案

答案:ABD。线程池决定“同时开几个灶台”,调度决定“菜按什么顺序下锅”,任务窃取让空闲大厨去帮忙。C 是常见误解:线程过多会导致上下文切换开销,延迟反而升高。

Q10. 工程实践中哪些手段能降低高优先级请求的 P99 延迟?

多选题 · hard

  • A.为高优先级请求预留专用推理资源← 正确答案
  • B.同优先级短请求合并成批推理← 正确答案
  • C.关闭全部监控日志减少系统开销
  • D.对队列深度和延迟配置自动告警← 正确答案

答案:ABD。预留专用资源相当于给红卡留一口专属灶台,批处理融合提升整体吞吐,监控告警让问题在 SLA 违约前暴露。C 看似省开销,实则让人变成瞎子,属于饮鸩止渴。