一台机器八张卡,四十个作业等着。 平台的活不是「让卡更快」,是「让卡别空着」。 这一节把排队这件事算清楚:不同规矩下等多久、谁会饿死、碎片长什么样。
13.1 到 13.3 备好了算力、硬件、软件。这一节把它们放在一起,交给一层调度去分。 它和 8.4 讲的「分布式人工智能」不是一回事: 那边是智能体之间怎么协作,这边是「人怎么排队用机器」。
一、平台在管什么
作业长什么样:什么时候到、要跑多久、要几张卡。 这三样东西一写下来,调度问题就变成了一个纯算术题。
| 作业 | 到达 | 时长 | 要几张卡 |
|---|---|---|---|
| #1 | 2 | 4 小时 | 2 |
| #2 | 11 | 6 小时 | 2 |
| #3 | 0 | 6 小时 | 8 |
| #4 | 13 | 7 小时 | 8 |
| #5 | 12 | 5 小时 | 1 |
| #6 | 10 | 7 小时 | 4 |
| 四十个作业加起来 540 个「卡乘小时」。八张卡全满跑要 67.5 小时。 | |||
| 这个数就是理想下界,真实平台永远达不到—— | |||
| 作业是零散到达的,卡不一定凑得齐,作业也不一定排得进。 |
二、三种排队规矩
规矩不同,等的时间差一倍多:
| 策略 | 平均等待 | 最长等待 | 全部跑完 | 卡利用率 |
|---|---|---|---|---|
| 先来先服务 | 26.2 小时 | 63 小时 | 90 小时 | 75.0% |
| 短作业优先 | 9.4 小时 | 51 小时 | 73 小时 | 92.5% |
| 小卡作业优先 | 10.2 小时 | 53 小时 | 74 小时 | 91.2% |
| 短作业优先两项都压下去了,利用率也从 75.0% 抬到 92.5%。 | ||||
| 因为它先干短的,卡腾得快,整个队列跟着都在动。 | ||||
| 原因不难想:平均等待这个指标,天然被少数长作业拖高。 | ||||
| 先把短的清掉,平均自然就低了——代价藏在第 3 段。 |
三、饿死与碎片
先看那个最重的作业:#4,要 8 张卡、跑 7 小时。它在三种规矩下等了多久?
| 策略 | 它等了多久 |
|---|---|
| 先来先服务 | 35 小时 |
| 短作业优先 | 45 小时 |
| 小卡作业优先 | 43 小时 |
| 先来先服务对它最友好,因为它来得早、又没人插队。 | |
| 短作业优先对它最狠——只要队列里还有比它短的,它就永远排不上。 | |
| 这就叫饿死:不是没资源,是永远轮不到。 | |
| 平均等待变好了,最差的那一个却更差了——这就是「优化平均值」的代价。 | |
| 真实平台怎么治?按队列分权重,再设一个最长等待上限。 | |
| 等过上限就强行提优先级:宁可浪费一点卡,也不能让一个作业无限期等下去。 | |
| 第二种麻烦叫碎片。两台机器,每台 4 张卡,先来四个单卡作业: |
| 节点 | 已占 | 空闲 | 能放下什么 |
|---|---|---|---|
| 节点甲 | 2 张 | 2 张 | 最多 2 卡的作业 |
| 节点乙 | 2 张 | 2 张 | 最多 2 卡的作业 |
| 现在来了一个要 4 卡、跨机不能拆的作业:看总数 4 张够,看单台每台只有 2 张。 | |||
| 这就是碎片的准确形状:不是卡不够,是「凑不成一整块」。 | |||
| 两条治法:把大作业单独划一个池子(分区),或者规定「要么整块给你,要么继续等」。 | |||
| 两种都是拿利用率换「大作业不被无限期拖住」。 |
四、不显眼的三件事
第一,实验记录。同一个模型跑二十次,哪次用了什么参数、哪份数据, 不记下来就复现不了。这一项比调度更影响长期效率。 第二,环境镜像。13.3 那组依赖的版本要一并存下来, 否则半年后同一个作业跑出不同结果,谁也说不清是谁的问题。 第三,配额。团队共用一套卡,没有配额就会有人一口气占满。 配额不是限制自由,是让「排队」这件事可预期。 这三件事都不产生算力,可少了它们,前面所有的卡都会白等。 平台的价值一半在调度,另一半就在这些没人爱做的杂事上。
五、动手 Demo:四十个作业抢八张卡
五段:作业与理想下界、三种排队规矩、最重的作业等多久、资源碎片、 平台还要管的三件事。 没有用任何调度系统,就是一个 while 循环加几个挑选函数。 时间按小时走,卡按张数分配——这么粗的模型,已经足够看出「饿死」和「碎片」。
def simulate(order):
left, t, wait = list(JOBS), 0, []
while left:
ready = [j for j in left if j[0] <= t] # 已经到达的
fit = [j for j in ready if j[2] <= FREE] # 卡够的
if not fit:
t += 1
continue
j = order(fit) # 按规矩挑一个
wait.append(t - j[0]) # 记下它等了多久
left.remove(j)
run.append((t, t + j[2], j[2])) # 这段卡被占住
t += 1 # 时间往前走一格
return wait
实跑输出:
--------------------------------------------------------------------
四十个作业抢八张卡
--------------------------------------------------------------------
==============================================================
[1] 作业长什么样
四十个训练作业,每个有到达时间、要跑多久、要几张卡:
作业 到达 时长(小时) 要几张卡
#1 2 4 2
#2 11 6 2
#3 0 6 8
#4 13 7 8
#5 12 5 1
#6 10 7 4
……(共 40 个)
所有作业加起来要 540 个「卡乘小时」。八张卡全满跑要 67.5 小时。
**这个数就是理想下界。真实平台永远达不到——**
**因为作业是零散到达的,卡不一定凑得齐。**
[2] 三种排队规矩,跑同一个队列
策略 平均等待 最长等待 全部跑完 卡利用率
先来先服务 26.2 小时 63 小时 90 小时 75.0%
短作业优先 9.4 小时 51 小时 73 小时 92.5%
小卡作业优先 10.2 小时 53 小时 74 小时 91.2%
**短作业优先两项都压下去了:平均 9.4 小时、最长 51 小时。**
**因为它先干短的,卡腾得快,整个队列跟着都在动。**
**可这两个数都是「整体」,不代表每个作业都好过——第 3 段看那个最大的。**
[3] 饿死:那个最大的作业等了多久
挑出最重的一个:要 8 张卡、跑 7 小时(作业 #4)。
策略 它等了多久
先来先服务 35 小时
短作业优先 45 小时
小卡作业优先 43 小时
**先来先服务对它最友好,因为它来得早、又没人插队。**
**短作业优先最狠——只要队列里还有比它短的,它就永远排不上。**
**这就是调度里的「饿死」:不是没资源,是永远轮不到。**
真实平台怎么治?**按队列分权重,再设一个最长等待上限。**
**等过上限就强行提优先级——宁可浪费一点卡,也不能让一个作业无限期等下去。**
[4] 资源碎片:卡是够的,可凑不齐
两台机器,每台 4 张卡。先来了四个单卡作业,两台各占两张:
节点 已占 空闲 能放下什么
节点甲 2 张 2 张 最多 2 卡的作业
节点乙 2 张 2 张 最多 2 卡的作业
现在来了一个要 4 卡、跨机不能拆的作业:
看总数 空闲 4 张,够
看单台 每台只有 2 张,都不够
**这就是碎片的准确形状:不是卡不够,是「凑不成一整块」。**
**四张单卡作业把两台机器各切剩一半,那个要 4 卡的作业只能等它们全跑完。**
两条治法:**把大作业单独划一个池子(分区)**,**或者干脆规定「要么整块给你,要么继续等」。**
**两种都是拿利用率换「大作业不被无限期拖住」。**
[5] 平台还要管的另外三件事
**第一,实验记录。**同一个模型跑二十次,哪次用了什么参数、哪份数据,
**不记下来就复现不了。这一项比调度更影响长期效率。**
**第二,环境镜像。**上一节那组依赖的版本要一并存下来,
**否则半年后同一个作业跑出不同结果,谁也说不清是谁的问题。**
**第三,配额。**团队共用一套卡,没有配额就会有人一口气占满。
**配额不是限制自由,是让「排队」这件事可预期。**
五段连起来:**作业零散到达,理想下界永远达不到**;
**短作业优先压平均等待,代价是饿死大作业**;
**所以真实平台按权重分队列,还得设等待上限**;
**卡够不一定凑得齐,碎片要单独治**;
**而记录、镜像、配额这三件不显眼的事,决定平台能不能长期用。**
第 1 段那个 67.5 小时是下界,任何策略都超不过它,只会更差。 实测最好的 73 小时,只比下界多跑五个半小时——已经算很好了。 第 2 段三行看「平均」会得出「短作业优先最好」。 第 3 段立刻把这个结论推翻:它对最大的那个最差,等了 45 小时。 第 4 段那个碎片,光看总数是看不出来的。 必须看「每一台还剩几张」——这也是真实平台按节点而不是按总数报空闲资源的原因。 第 5 段那三件事没有一行代码。 可一个平台能不能长期用,决定性的往往就是这三件:记得住、搬得走、排得公平。
30 秒实验
- 把第 2 段的「短作业优先」改成「长作业优先」,重跑。 平均等待会跳到最差——这条规矩专挑平均值最痛的地方下手,正好反证了第 2 段那个结论。
- 在第 3 段给「短作业优先」加一条最长等待 30 小时的上限,重跑。 #4 的等待会降下来,代价是平均等待往上走一点——这就是「治饿死」要付的钱。
- 把第 4 段改成三台机器、每台 3 张卡,重新摆一遍碎片。 要 3 卡的作业同样卡住——碎片的形状跟卡的总数无关,只跟「单块最大能凑多少」有关。
常见误区
- 以为「卡多就不排队」。作业是零散到达的,四十个作业抢八张卡,排队是常态。 平台的目标不是消灭排队,是让等待可预期、可解释。
- 以为「短作业优先就是最优」。它把平均值做到了最好, 代价是长作业可能永远排不上——这叫饿死,是所有「按优先级排队」的通病。
- 以为「利用率低就是有人在偷懒」。碎片也能造成低利用率: 卡是空着的,可凑不出一整块,大作业照样进不来。
- 以为「平台就是个分卡的脚本」。调度只占一半。 实验记录、环境镜像、配额,这三件不显眼的事才决定它能不能长期用。
自测三问(点开对答案)
1. 为什么理想下界永远达不到? —— 因为**作业不是一起到的,卡也不一定凑得齐**。 **Demo:理想 67.5 小时,先来先服务跑了 90 小时。** **下界只说明「再怎么排也不可能更快」,不是「应该达到」。** 2. 「饿死」是怎么发生的? —— 因为**总有人比它更「值得先跑」**。 短作业优先之下,**只要队列里还有比 #4 短的,它就永远轮不上——它等了 45 小时。** **治法不是换规矩,是加一条最长等待上限。** 3. 卡总数够,为什么作业还是进不来? —— 因为**碎片**。 Demo 第 4 段:两台各 4 卡,各剩 2 张,**总数 4 张够,可没有一个节点能给出 4 张。** **所以要按节点看空闲,或者给大作业单独划池子。**第 13 章到这里结束。 下一章换到数据那一侧:当数据多到一台机器装不下、多到十年都存不完,会发生什么。 数据膨胀、物联网、脑功能分析、创新系统,四节收尾。
- ☐ (本章完,下一节 14.1 数据膨胀)