周五晚高峰,一家米其林餐厅的后厨乱成一锅粥。张师傅的爆炒台前只排着 3 张单子,他闲得擦锅;王师傅的甜品台前却摞着 30 张舒芙蕾订单,他手忙脚乱。领班老陈一拍脑门:“我明明按 1、2、3 号顺序轮流派单,怎么乱成这样?”问题就出在“轮流”上——张师傅颠一锅菜只要 1 分钟,王师傅做一份甜品要 5 分钟,两人拿到同样数量的单,慢的那位必然越积越多。忙的忙死,闲的闲死,整条出菜通道被最慢环节卡死。 这套后厨场景搬进计算机,就是异构计算调度要解决的核心问题。AI 系统里同时存在 GPU、CPU、NPU 等算力差异巨大的设备,如果调度器只是机械地轮流分配任务,快的设备等任务,慢的设备堆任务,系统整体吞吐量被短板死死拖住。聪明的领班懂得:根据每位厨师的真实速度和当前手头的单量,动态地把新订单分给“最不忙的合适人选”。这个思路,就是负载均衡(Load Balancing)。它让每一块芯片都像被合理安排的大厨——忙而不乱,持续出菜。
负载均衡是 AI Runtime 系统调度器的核心能力。掌握它,你就能理解 GPU/CPU/NPU 集群上的任务如何被高效分发、推理服务为何能丝滑应对突发流量,为设计高性能推理系统打下关键基础。
可视化演示
通过静态分配、动态调度与设备利用率对比,直观展示异构计算系统如何均衡分配任务以最大化吞吐量。
本节配有交互动效(进入视口后自动加载,约 3KB)。
静态负载均衡 任务剖面预分析 建立执行时间矩阵 T[task][dev] 贪心调度据此分配任务。
1. 负载均衡概念
周五晚高峰,后厨乱成一锅粥。张师傅的爆炒台前只排了 3 张单,王师傅的甜品台前却摞了 30 张舒芙蕾订单。老陈一拍脑门:“我明明按 1、2、3 号顺序轮流派单,怎么乱成这样?”问题就出在“轮流”上——张师傅颠一锅菜只要 1 分钟,王师傅做一份甜品要 5 分钟,两人拿到同样数量的单,慢的那位必然越积越多。忙的忙死,闲的闲死,整条出菜通道被最慢环节卡死。
这套后厨场景搬到计算机里,就是异构计算调度面临的核心问题。AI 系统里通常同时存在 GPU、CPU、NPU 等算力差异巨大的设备,它们各自的处理速度、擅长任务都不同。如果调度器只是机械地轮流分配任务,快的设备会闲等,慢的设备会堆积,系统整体吞吐量被短板设备死死拖住。负载均衡要解决的,正是“如何让每台设备都忙起来,且忙得恰到好处”。
从工程定义看,负载均衡是指调度器根据设备算力差异、当前负载和任务特性,动态地为每个新任务选择落地设备,使各设备利用率维持在合理区间,最终最大化系统吞吐量和资源利用效率。衡量一台设备忙不忙,最直接的指标是利用率——统计周期内设备真正干活的时间占比:
老陈后来换了思路:不再机械轮流,而是先看每位师傅手头还有多少菜,再把新单子塞给最空的人。几位师傅的灶台终于同时动了起来,出菜速度翻倍。这个朴素的道理,就是负载均衡最底层的直觉——让每一份资源都物尽其用。
- 分子 $T_{\text{busy},i}$设备 $i$ 在统计窗口内真正执行计算任务的时间,比如 GPU 跑矩阵乘法的时长。
- 分母 $T_{\text{total},i}$整个统计窗口的长度,例如 1 秒或 1 分钟。
- 比值 $U_i$两者相除得到 0 到 1 之间的利用率,越接近 1 说明设备越饱和。
2. 异构设备差异
老陈心里有一本“厨师能力手册”:张师傅管 8 个爆炒灶,火力猛、翻锅快,最适合做快炒;李师傅的灶台慢些但什么菜系都会;王师傅的甜品炉虽小,做起招牌舒芙蕾却比谁都专业。给张师傅派红烧肉、给王师傅派爆炒腰花,都是在浪费人才。
异构系统的设备差异也是如此。GPU 拥有数千个计算核心,擅长大规模并行矩阵运算,是深度学习训练和推理的主力;CPU 核心少但逻辑控制能力强,适合处理调度、数据预处理等串行任务;NPU 是专为神经网络算子设计的加速器,处理量化后的推理任务时功耗低、速度快。三者就像能力画像完全不同的三位厨师。
调度器眼里,每台设备有三个关键属性:峰值算力 $C_i$(单位时间能完成多少计算)、内存带宽(原料搬运速度)、任务亲和度(这个活它干得快不快)。真正能交付的吞吐,不是峰值算力,而是峰值、利用率、亲和度三者的乘积:
老陈分配单子前,先翻能力手册再估当前忙闲:爆炒交给张师傅,甜品交给王师傅,没人有空才让李师傅顶上。调度器同样在“谁适合”和“谁有空”之间做权衡,这正是负载均衡的第二层智慧。
- $C_i$设备 $i$ 的峰值算力,描述它全力工作时单位时间能完成的计算量。
- $U_i$设备当前的利用率,给峰值算力打一个“忙碌折扣”。
- $A_{i,k}$任务 $k$ 与设备 $i$ 的亲和度,取值 0–1,表示任务类型适不适合这台设备。
- 三者连乘得到设备对该任务的有效吞吐,调度器用它来决定把任务派给谁。
3. 调度器架构
后厨墙上钉着一排订单夹,新客人的单子先挂在夹子上排队;老陈站在订单夹前,眼睛不停扫过三个灶台,心里盘算下一单交给谁。这个“看单—判断—派发—再看”的循环,从早到晚没有停过一秒。
在 AI Runtime 系统里,扮演老陈的就是调度器(Scheduler)。它的架构通常由三部分构成:全局任务队列(订单夹)、设备状态表(老陈的记忆)、分发引擎(派单的手)。任务到达后先进入队列,调度器周期性读取各设备状态,计算分发决策,把任务派给目标设备。
调度器的工作可以抽象成一个决策循环:每个时间片,从队列取任务 → 查询各设备负载 → 计算最合适的目标 → 派发并更新状态。这个循环的数学表达,就是“选择当前相对负载最低的设备”:
老陈的循环看似简单,难在坚持:哪怕只有一桌客人,他也会实时关注每位师傅的灶台变化。调度器同样以毫秒级周期不停运转,因为负载是流动的,平衡也必须是动态的。
- $L_i(t)$$t$ 时刻设备 $i$ 尚未处理完的任务量,即积压。
- $C_i$设备 $i$ 的处理能力,相当于厨师的出菜速度。
- $\frac{L_i}{C_i}$把积压除以速度,得到“清空积压还要多久”的相对忙碌度。
- $\arg\min$在所有设备里找出该比值最小的那一个,作为派单目标。
4. 负载度量方法
老陈不只看灶台上的火苗,他还会数每位师傅手边没做完的订单张数、估算每道菜还要几分钟。对他来说,“忙”不是一个模糊感觉,而是一把能量化的尺子:把灶台前所有订单做完,还要多少分钟?
计算机里的负载度量,同样不能只看单一指标。设备利用率反映的是“过去一段时间”的状况,有滞后性;队列长度反映积压,但没考虑任务大小;平均执行时间反映成本,但缺少实时性。工程上把它们合成一个负载指数。
一种常用的负载指数,把积压任务数、平均执行时间和设备能力结合起来:
有了这把尺子,老陈不再凭感觉分单,而是时刻在心里给每位师傅打一个“清空时间分”。调度器的负载度量,就是它心里那把永远在更新的尺——尺子准,平衡才有基础。
- $Q_i$设备 $i$ 队列中等待的任务数量,相当于厨师手边积压的订单张数。
- $\overline{T}_{i}$该设备上任务的平均执行时长,估算每张订单平均要花多少分钟。
- $Q_i \times \overline{T}_{i}$两者相乘,得到尚未完成的总工作量。
- 除以 $C_i$把总工作量用设备能力归一化,得到可比较的负载指数。
5. 调度算法策略
面对新到的订单,老陈有三种派法:第一种,按厨师编号轮流来,一人一张;第二种,谁手头最空就给谁;第三种,根据每位师傅的出菜速度按比例分配——张师傅快,多给他,王师傅慢,少给他。当过领班的都知道,第一种最省事也最容易翻车。
调度算法也有同样的阶梯:轮询调度(Round-Robin)实现简单但不考虑设备差异;最少负载调度(Least-Loaded)实时找最闲的设备,已经比轮询聪明;加权调度(Weighted)则把设备算力差异直接变成分配比例,是工程上的主流选择。
加权调度的核心,是把新任务按设备算力占比来分发。设系统有 $n$ 台设备,设备 $i$ 的算力为 $C_i$,则它应获得的长期任务份额为:
老陈把三种派法都试过之后,最终采用了“加权 + 实时修正”的组合拳:平时按速度比例分,某位师傅临时积压时立刻把新单转给别人。纯比例是蓝图,实时修正才是让蓝图落地的方向盘。
- 分子 $C_i$单台设备的算力,比如 GPU 为 50 TFLOPS。
- 分母 $\sum_{j=1}^{n} C_j$所有设备算力的总和,相当于整个后厨的总出菜能力。
- 比值 $W_i$设备 $i$ 在系统能力中的占比,也是它应承担的任务份额。
6. 动态负载调整
周五晚上 8 点,甜品区突然爆单——10 桌客人同时点了舒芙蕾。王师傅的甜品炉前订单从 3 张涨到 20 张,而张师傅的爆炒台已经闲下来了。老陈立刻把一部分舒芙蕾单子抽出来,交给受过甜品培训的备用小灶去做。这一抽一补,就是后厨版的“任务迁移”。
现实中的推理负载同样会突发:某个大模型突然被大量用户访问,专属 GPU 显存吃紧、队列飞涨,而其他设备还在空转。仅仅靠静态加权分配远远不够,系统必须动态感知失衡并主动调整,这就是动态负载均衡。它包含四件事:实时监控、失衡判定、任务迁移、反馈调节。
失衡判定的量化方法是:计算设备 $i$ 的相对负载与系统平均相对负载的偏差,超过阈值 $\theta$ 就触发调整:
老陈并不会为一张订单的波动就大叫“迁移”,只有当甜品区明显积压时才动手。迁移本身有成本——抽单、交接、重新备料都需要时间。好的动态调整,是在“不平衡的损失”和“迁移的成本”之间找到平衡点。
- $\frac{L_i}{C_i}$设备 $i$ 当前的相对负载。
- $\frac{1}{n}\sum_{j=1}^{n}\frac{L_j}{C_j}$所有设备相对负载的平均值,即后厨的平均忙碌水位。
- $\delta_i$两者相减得到偏差,正数表示比平均更忙。
- 阈值 $\theta$偏差超过它才迁移,避免为一点微小波动就频繁搬运任务、白白消耗资源。
7. AI推理应用
周末的餐厅迎来了最真实的考验:上百位客人同时涌进来,点的菜五花八门,而且每个人都有心理底线——等超过 15 分钟就摔门走人。后厨必须做到:出菜要快(低延迟),同时尽量接更多客人(高吞吐),还要保证每桌都能上菜(公平性)。
这正是 AI 推理服务部署的日常。多模型推理集群上,每秒钟都有大量请求到达,调度器要把它们分发给 GPU/CPU/NPU 上的模型副本。常用优化手段包括:按亲和度选择最合适的设备、动态调整批处理大小(batch size)、在设备过载时迁移请求。批处理就像同时烤 12 个舒芙蕾——吞吐倍增,但第一批客人要多等一会儿。
吞吐与延迟之间的权衡,可以用排队论中的 Little's Law 描述:系统中的在途请求数等于请求到达率乘平均处理时间。
当老陈学会“批量做同款菜 + 动态平衡灶台”这套打法,后厨才能在爆满的周末既保住翻台率又守住客人的耐心。AI 推理集群的负载均衡,做的正是同一件事:让每一块芯片都像被合理安排的大厨,忙而不乱,持续出菜。
- $\lambda$请求到达速率,即每秒有多少新推理请求进入系统。
- $\overline{W}$单个请求从进入到完成平均花费的时间。
- 乘积 $N$两者相乘得到系统中同时“在途”的请求数,相当于后厨里所有在做和等待的菜。
- 工程启示在途请求越多,平均延迟越高;负载均衡让在途请求均匀分散到各设备上,从而在吞吐与延迟之间取得最优折中。
AI 应用场景
多租户 GPU 推理服务
阶段:AI 推理系统部署与在线调度
解决的问题:多个租户共享 GPU 集群时,传统固定比例分卡会导致某块 GPU 排队爆炸、其他 GPU 空转。每次新模型上线都要人工调卡,运维成本高且响应慢。负载均衡让调度器按实时负载自动路由请求,无需人工干预。
具体案例:某云推理平台把 8 块 GPU 分给多个模型服务,调度器每 100ms 采集各 GPU 的利用率与队列深度,新请求按“负载指数最小”原则路由;当某 GPU 队列超过阈值时,把排队请求迁移到空闲 GPU。上线后系统整体吞吐提升约 2.3 倍,P99 延迟下降 40%。
LLM 多副本负载分发
阶段:大规模语言模型推理加速
解决的问题:单一模型副本扛不住尖峰流量,多个副本之间显存与 KV cache 占用不均,导致部分副本请求排队过长、部分副本资源闲置。负载均衡让前端调度器把请求分给最“空”的副本。
具体案例:部署 4 个 vLLM 模型副本,前置调度器根据每个副本“KV cache 占用率 + 队列长度”动态分发请求;当某个副本接近显存上限时,把它的新请求转给其他副本,同时迁移部分存量会话,保证长对话请求不被中断。
端云协同调度
阶段:边缘侧模型部署
解决的问题:手机 NPU 算力有限但省电低延迟,云 GPU 算力强但有网络往返开销。全上云成本高且响应慢,全本地则复杂模型跑不动。端云负载均衡根据任务复杂度和设备负载决定谁来干活。
具体案例:语音助手的端侧调度器把唤醒词识别放在本地 NPU 秒级响应,把复杂语义理解上传云端 GPU;当手机 NPU 因本地热点任务负载升高时,动态把一部分简单任务也转给云端,避免端侧过载导致卡顿。
Python 演示
class Device: """异构设备: 算力不同, 当前积压任务量也不同.""" def __init__(self, name, capacity): self.name = name self.capacity = capacity self.load = 0.0 def ratio(self): """相对负载 = 积压 / 能力, 越低越空闲.""" return self.load / self.capacity def finish(self, rate): """每个时间片完成一部分任务, 负载自然回落.""" self.load = max(self.load - rate, 0.0) class LoadBalancer: """调度器: 新任务派给相对负载最低的设备, 失衡时迁移任务.""" def __init__(self, devices): self.devices = devices self.total = 0 def dispatch(self, amount=0.06): best = min(self.devices, key=lambda d: d.ratio()) best.load = min(best.load + amount, 1.0) self.total += 1 return best.name def rebalance(self, threshold=1.2): """最忙设备相对负载超阈值时, 把部分任务迁给最闲设备.""" src = max(self.devices, key=lambda d: d.ratio()) if src.ratio() <= threshold: return None dst = min(self.devices, key=lambda d: d.ratio()) moved = src.load * 0.3 src.load -= moved dst.load = min(dst.load + moved, 1.0) return (src.name, dst.name) def main(): balancer = LoadBalancer([ Device("GPU", 0.5), Device("CPU", 0.3), Device("NPU", 0.2), ]) print("step\tGPU\tCPU\tNPU") for t in range(60): balancer.dispatch() for d in balancer.devices: d.finish(0.02) balancer.rebalance() if t % 10 == 9: ratios = [int(d.ratio() * 100) for d in balancer.devices] print("%d\t%d%%\t%d%%\t%d%%" % (t + 1, ratios[0], ratios[1], ratios[2])) if __name__ == "__main__": main()
class Device:定义异构设备,capacity是算力,load是当前积压任务量。def ratio(self):计算相对负载 = 积压 ÷ 能力,这是调度比较的核心尺子。best = min(self.devices, key=lambda d: d.ratio())找出相对负载最低的设备,负载均衡的核心决策。best.load = min(best.load + amount, 1.0)新任务加到目标设备上,上限 1.0 防止负载溢出。def rebalance(self, threshold=1.2):动态调整:找出最忙设备,判断是否超过迁移阈值。moved = src.load * 0.3把最忙设备 30% 的任务抽出来,迁给最闲设备。balancer.rebalance()每个时间片检查一次失衡,形成“监控—判定—迁移”反馈闭环。print("%d\t%d%%\t%d%%\t%d%%" % ...)打印三台设备的负载比,观察它们是否逐渐趋平。
常见误区
误区我觉得负载均衡就是均分,比如 3 台设备就每个分三分之一,这样最公平。
正解均分只对同构系统成立。异构设备算力不同,均分反而让慢设备首先过载、快设备挨饿。均衡的目标是让“负载 ÷ 容量”这个比值趋同。就像老陈派单,不是一人 5 张,而是快手多给、慢手少给,最终所有人“清空订单所需时间”接近。
为何常见:“均衡”二字容易让人联想到数学里的平均分配,忽略了分母(容量)的存在。
误区利用率 100% 说明设备没有浪费,当然越好,最好每台设备一直满负荷。
正解利用率过高意味着队列积压、延迟飙升,系统处在过饱和边缘;突发流量一到,延迟会剧烈恶化。负载均衡追求的是“合适的利用率 + 可控的排队”,而不是把设备压到极限。有经验的领班,会刻意让师傅们保留一点余力应付突发。
为何常见:直觉上“忙 = 高效”,但系统层面“持续满负荷”和“健康运行”是两回事。
误区既然已经按算力比例分任务了,那就一直按这个比例执行,不用再管。
正解加权比例只反映长期平均值,真实负载有突发、有局部热点,某类任务可能集中打向同一台设备。现代调度器必须叠加实时负载监控和任务迁移,像老陈那样根据灶台实况随时调整,而不是死守年初定的分配表。
为何常见:静态分配模型简单、容易理解,容易让人忽略现实负载的波动性。
误区既然迁移能恢复平衡,那只要有一点偏差就赶紧搬任务,越早搬越好。
正解任务迁移需要序列化状态、传输数据、重建上下文,成本往往不低。频繁迁移会让系统花在“搬”上的时间比“做”还多。工程上设置阈值,只有当失衡超过一定程度才迁移,就像老陈不会为一张甜品订单就喊人换岗。
为何常见:看到迁移带来的好处,却忽略了迁移本身的代价,缺少成本收益意识。
面试题
Q1. 负载均衡最核心的目标是什么?
- A.让每台设备接收相同数量的任务
- B.提升系统整体吞吐和资源利用← 正确答案
- C.总是把任务交给算力最强的设备
- D.确保任务在队列中充分排队等待
答案:B。负载均衡不追求均分(A 错),也不追捧单点最强(C 错),更不是故意排队(D 错)。它的核心是让各设备利用率匹配其能力,使整体吞吐最大化,如同让每位厨师都忙得有价值。
Q2. 轮询调度在异构系统中的主要缺陷是什么?
- A.实现代码复杂且难以调试维护
- B.需要预先掌握每个任务的计算量
- C.忽略设备算力差异导致慢设备堆积← 正确答案
- D.只适用于设备数量不超过两台场景
答案:C。轮询实现很简单(A 错),不需要知道任务内容(B 错),也不限于两台设备(D 错)。它机械地一人一单,忽略了异构设备的算力差异,慢设备必然积压,如同让慢厨和快厨拿到相同单量。
Q3. “平均分配任务”为什么不等同于“负载均衡”?
- A.因为任务总数总是大于设备的总数
- B.因为平均分配会使设备完全停止工作
- C.因为调度器无法统计任务的总数量
- D.因为设备算力不同均分会拖垮弱设备← 正确答案
答案:D。均分只有在设备能力相当时才合理。异构系统里弱设备处理慢,均分会让它先过载,强设备却闲着。均衡的本质是比较“负载 ÷ 容量”而不是任务个数,正如给快手多派单才是真公平。
Q4. 加权分配中,设备算力为 40/40/20 时,第一台设备的任务份额最接近?
- A.0.2
- B.0.4← 正确答案
- C.0.5
- D.1.0
答案:B。份额 = 40 ÷ (40+40+20) = 0.4。0.2 是第三台设备的份额,0.5 和 1.0 分别是算力占比 50% 和 100% 时才会出现。加权分配让每台设备按能力比例承担任务。
Q5. 负载指数 Load_i = (Q_i × T̄_i) / C_i 中,Q_i 代表什么?
- A.设备当前的实时利用率百分比
- B.队列中尚未完成的任务数量← 正确答案
- C.单个任务的平均执行时长
- D.设备处理能力的峰值大小
答案:B。Q_i 是队列积压的任务数;T̄_i 才是平均执行时长(C 错);C_i 是能力(D 错);利用率是另一类滞后指标(A 错)。负载指数估算的是“清空积压需要多久”。
Q6. 任务亲和度 A_{i,k} 描述的是什么?
- A.任务使用语言与设备厂商的匹配
- B.设备之间物理距离的远近程度
- C.任务类型契合设备算力特点程度← 正确答案
- D.任务发热对设备散热的压力大小
答案:C。亲和度描述“这个活适不适合这台设备干”。矩阵运算配 GPU、控制逻辑配 CPU,亲和度高才高效。语言匹配(A)、物理距离(B)、散热压力(D)都不是任务亲和度的含义。
Q7. Little's Law 中 N = λ × W̄ 给调度器最重要的启示是?
- A.到达率越高则平均延迟必然越低
- B.吞吐与延迟之间存在此消彼长关系← 正确答案
- C.处理时间与在途请求数完全无关
- D.吞吐量只取决于设备的峰值算力
答案:B。λ 越高或 W̄ 越长,在途请求 N 就越大,排队延迟随之上升。追求高吞吐(高 λ)和低延迟(低 W̄)天然矛盾,负载均衡通过压缩空闲、缩短等待来同时改善两者。
Q8. 下列哪些属于动态负载调整的常见手段?
- A.实时监控各设备的负载指标← 正确答案
- B.将过载设备的部分任务迁移← 正确答案
- C.为设备设定永久固定的权重
- D.根据反馈结果调节分发策略← 正确答案
答案:ABD。动态调整的核心是“监控—判定—迁移—反馈”闭环,A、B、D 都在其中。C 是静态加权分配,没有实时反馈,不属于动态手段,就像老陈永远按旧比例派单。
Q9. 设备相对负载为 0.6、1.4、0.8,平均值为 0.93,阈值 θ=0.4,需要触发迁移的是?
- A.相对负载为 0.6 的设备
- B.相对负载为 0.8 的设备
- C.相对负载为 1.4 的设备← 正确答案
- D.所有设备都触发迁移
答案:C。偏差 δ 分别为 -0.33、0.47、-0.13,只有 1.4 的设备 δ=0.47 ≥ 0.4 超过阈值。迁移只针对明显过载的设备,0.6 和 0.8 的设备低于平均、无需调整。
Q10. AI 推理服务负载均衡调度的输入因素包括哪些?
- A.模型对设备显存容量的约束← 正确答案
- B.设备当前队列积压与任务耗时← 正确答案
- C.请求类型与设备的亲和度← 正确答案
- D.批处理大小对端到端延迟的影响← 正确答案
答案:ABCD。推理调度是典型的综合决策:显存决定模型能不能放(A 对),队列与耗时决定谁更空(B 对),亲和度决定谁更快(C 对),批大小决定吞吐与延迟的取舍(D 对)。忽略任一项都会导致局部失衡。