📘 第13章 分布式计算
📄 一句话概括

一台机器八张卡,四十个作业等着。 平台的活不是「让卡更快」,是「让卡别空着」。 这一节把排队这件事算清楚:不同规矩下等多久、谁会饿死、碎片长什么样。

📝 本节和前面几节的关系

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 秒实验

  1. 把第 2 段的「短作业优先」改成「长作业优先」,重跑。 平均等待会跳到最差——这条规矩专挑平均值最痛的地方下手,正好反证了第 2 段那个结论。
  2. 在第 3 段给「短作业优先」加一条最长等待 30 小时的上限,重跑。 #4 的等待会降下来,代价是平均等待往上走一点——这就是「治饿死」要付的钱。
  3. 把第 4 段改成三台机器、每台 3 张卡,重新摆一遍碎片。 要 3 卡的作业同样卡住——碎片的形状跟卡的总数无关,只跟「单块最大能凑多少」有关。

常见误区

  1. 以为「卡多就不排队」。作业是零散到达的,四十个作业抢八张卡,排队是常态。 平台的目标不是消灭排队,是让等待可预期、可解释。
  2. 以为「短作业优先就是最优」。它把平均值做到了最好, 代价是长作业可能永远排不上——这叫饿死,是所有「按优先级排队」的通病。
  3. 以为「利用率低就是有人在偷懒」。碎片也能造成低利用率: 卡是空着的,可凑不出一整块,大作业照样进不来。
  4. 以为「平台就是个分卡的脚本」。调度只占一半。 实验记录、环境镜像、配额,这三件不显眼的事才决定它能不能长期用。
自测三问(点开对答案) 1. 为什么理想下界永远达不到? —— 因为**作业不是一起到的,卡也不一定凑得齐**。 **Demo:理想 67.5 小时,先来先服务跑了 90 小时。** **下界只说明「再怎么排也不可能更快」,不是「应该达到」。** 2. 「饿死」是怎么发生的? —— 因为**总有人比它更「值得先跑」**。 短作业优先之下,**只要队列里还有比 #4 短的,它就永远轮不上——它等了 45 小时。** **治法不是换规矩,是加一条最长等待上限。** 3. 卡总数够,为什么作业还是进不来? —— 因为**碎片**。 Demo 第 4 段:两台各 4 卡,各剩 2 张,**总数 4 张够,可没有一个节点能给出 4 张。** **所以要按节点看空闲,或者给大作业单独划池子。**

第 13 章到这里结束。 下一章换到数据那一侧:当数据多到一台机器装不下、多到十年都存不完,会发生什么。 数据膨胀、物联网、脑功能分析、创新系统,四节收尾。

相关笔记