返回博客

2026.10.28

PCIe 4.0 x16 对 2.0 x4,纸面差 15.75 倍,加载模型实测只差 2.6 倍:给任务挑机器,加载时间该怎么算?

家里三台机器跑模型,我给自己写了个小调度器,按「谁先跑完」派任务。加上模型加载时间之后,同一个 9B 推理任务在 5 千 token 时该去 4090,在 5 万 token 时该去那台矿卡机。这篇给两个除法:一个算加载时间到底卡在哪一段,一个算两台机器的胜负在多少 token 处翻转。顺带讲我怎么一直以为矿卡跑在 x16 上,以及为什么三个规格不同的插槽,跑出了同一个 x4。

推理硬件PCIe方法论

一边是 4090,PCIe 4.0 x16。另一边是我那几张解锁过的矿卡,PCIe 2.0 x4。

按纸面带宽,前者是后者的 15.75 倍。把一个模型从硬盘塞进显存,4090 应该快一个数量级吧?

实测:4090 每秒 1.88 GB,矿卡 0.72 GB。只差 2.6 倍。

更好玩的是,加上这笔加载时间以后,同一个 9B 模型的推理任务,短的该派给 4090,长的该派给矿卡,分界线大约在 2 万个 token。

想直接拿公式的看第 3、4 章,两个除法;想知道「插槽写着 x8」为什么不算数,看第 2 章。

1. 起因:调度器一直把加载时间当成 0

家里有三台能跑模型的机器:一台 4090,一台插了三张 64GB 矿卡的机器,一台 64GB 的 Mac。机器一多,「这个活该扔给谁」就成了问题,所以我写了个小调度器,规则很朴素:

预计完成时间 = 排队要等多久 + 算完要多久

谁的数字小就派给谁。「算完要多久」按速度表查,比如某个 9B 模型在矿卡上解码每秒 146 个 token,在 4090 上 129 个。

问题是这里面少了一项:模型得先从硬盘读进显存。我原来把它记成 0,理由是「几十秒的事,影响不大」。

后来我意识到这个「几十秒」在不同机器上不一样。4090 插在 PCIe 4.0 x16 上,矿卡是被锁过的老链路,同样 20GB 的权重,读进去的时间差得可能比算的时间还多。短任务尤其吃亏,算 30 秒、加载 30 秒,加载那一项直接决定输赢。

所以得补上。补之前先量。

2. 先量链路:我记错的那个 x16

我一直以为那几张矿卡跑在 PCIe 2.0 x16 上。查了一下当前链路,三张卡全是 2.0 x4。

紧接着我又想起来,主板上那三个插槽明明不一样:两个是 CPU 直连的 x8 槽,一个是芯片组引出来的 x4 槽。插在 x8 槽里的卡,怎么也只跑 x4?

这个对上了,反而说明问题。三个能力不同的插槽,跑出一模一样的链路,那限速的就不在插槽,在卡自己。翻回我当初折腾这批卡的笔记:

  • 出厂链路锁在 Gen1,解锁补丁能抬到 Gen2。我这里的实测是从 0.85 GB/s 涨到 1.68 GB/s
  • Gen3 往上,我当时记下的结论是对应的熔丝已经烧掉,软件改不回来
  • 通道数只有 x4,是因为卡金手指那排小电容被拆了。想上 x16 得拿烙铁焊回去

所以换槽没用,三张卡的加载速度可以共用一个数。

这里有个能带走的判据:插槽上印的是上限,卡实际协商出来的链路才是你拿到的。看实际链路用这条:

nvidia-smi --query-gpu=pcie.link.gen.current,pcie.link.width.current --format=csv

最好在卡有负载的时候查。很多卡空闲时会主动降速省电,空着查到的代数偏低,别吓自己。

3. 再量加载:为什么只差 2.6 倍

我用真实的加载函数,把 safetensors 权重文件读进显存,读之前清掉系统缓存。然后同一个文件立刻再读一次,这回文件已经在内存的页缓存里了。

           冷读(清缓存)   热读(文件已在内存)   纯链路(内存→显存)
4090       1.88 GB/s        4.91 GB/s             26.36 GB/s
矿卡       0.72 GB/s        1.43 GB/s             1.69 GB/s

先看最右边一列。纸面带宽 PCIe 4.0 x16 约 31.5 GB/s,2.0 x4 约 2.0 GB/s。两边实测都拿到了 84% 到 85%,链路本身很老实。

再看中间那列,问题就来了。4090 的文件都已经在内存里了,读进显存也只有 4.91 GB/s,链路那 26 GB/s 只用了不到两成。说白了,4090 这边链路根本不是瓶颈,慢在加载器把文件拆成张量、一块块往显存搬的那个过程。

矿卡这边正好相反:热读 1.43,链路 1.69,基本贴着链路跑。它是真被那根 x4 卡住了。

冷读和热读的差,就是硬盘那一段的份。这是全文第一个能自己算的东西:

每 GB 耗在硬盘上的秒数 ≈ 1 ÷ 冷读速度 − 1 ÷ 热读速度
每 GB 耗在其他环节的秒数 ≈ 1 ÷ 热读速度

代进去:

4090:1/1.88 − 1/4.91 = 0.53 − 0.20 = 0.33 秒/GB 在硬盘,0.20 秒/GB 在别处
矿卡:1/0.72 − 1/1.43 = 1.39 − 0.70 = 0.69 秒/GB 在硬盘,0.70 秒/GB 在别处

4090 有六成多时间花在等硬盘。矿卡两段各占一半,硬盘和链路一样慢。

所以「15.75 倍」变成「2.6 倍」,原因很简单:快的那台,链路之前的环节先把它拖住了。链路宽 15 倍,只在最后一段生效,前面的硬盘和加载器两边差得没那么多。

这也是我最想让读者带走的一条:看到「PCIe 4.0 加载快一倍」这种说法,先问一句,数据在不在内存里。 冷启动读硬盘,链路宽度经常连主角都算不上。拿 cudaMemcpy 测出来的链路带宽去推加载时间,在快链路上能高估好几倍。

4. 胜负在多少 token 处翻转

有了加载速度,预计耗时就变成:

耗时 ≈ 要生成的 token 数 ÷ 解码速度 + 权重体积 ÷ 加载速度

拿一个 9B 模型算三个情况,权重体积按 20 GiB(约 21.5 GB)报给调度器:

5 万 token,不算加载:
  矿卡  50000/146 = 342 秒       4090  50000/129 = 388 秒   → 矿卡快
5 万 token,算加载:
  矿卡  342 + 21.5/0.72 = 372 秒  4090  388 + 21.5/1.88 = 399 秒   → 只差 7%
5 千 token,算加载:
  矿卡  34 + 30 = 64 秒          4090  39 + 11 = 50 秒      → 4090 快

不算加载的时候,矿卡一直赢。算上加载,短任务归 4090,长任务归矿卡。那分界线在哪?两边耗时相等,解出来是第二个公式:

翻转点 token 数 = 权重体积 × (1/慢加载 − 1/快加载) ÷ (1/慢解码 − 1/快解码)

这里「慢加载」是矿卡 0.72,「快加载」是 4090 1.88;「慢解码」是 4090 的 129,「快解码」是矿卡 146。代进去:

分子:21.5 × (1/0.72 − 1/1.88) = 21.5 × 0.857 = 18.4 秒
分母:1/129 − 1/146 = 0.000903 秒/token
翻转点 ≈ 18.4 ÷ 0.000903 ≈ 2 万 token

这个式子的意思一眼就能看出来:分子是「加载上吃的亏」,分母是「每多一个 token 赚回来多少」。矿卡在加载上亏 18 秒,每个 token 只赚回不到 1 毫秒,要生成 2 万个 token 才能把亏的追平。

我的调度器还有一条规则:两边差不到 10% 就算平手,交给别的条件决定。上面 5 万 token 那组差 7%,就被判了平手,最后派给了 4090。按这个口径,矿卡要到大约 9.8 万 token 才算「明确更快」。10% 这个数是我拍的,换成你的口径,翻转点会挪。

改完以后我用三单真任务验了派单:5 万 token 不写加载量的去了矿卡,写上 20G 加载量的去了 4090(平手),5 千 token 写加载量的直接去了 4090。三单的预计时间和上面手算的一致。

5. 这几个数哪些靠得住,哪些靠不住

说实话,这套东西我只验到了「派单逻辑对」,没验到「预测准」:

  • 那三单任务是故意让它跑一下就退出的,为的是不把假数据写进速度表。所以我只证明了调度器按公式在选机器,没证明 372 秒、399 秒真的接近实际耗时
  • 146 和 129 这两个解码速度,来自速度表里早先录入的数据,每格就一个样本,这次没有重测。翻转点对它们很敏感:分母是两个倒数的差,解码速度差得越小,翻转点越往后推
  • 我量的「加载」只是把权重读进显存。真实的推理服务启动还有初始化、编译、分配缓存这些步骤,这部分没算进去,而且在不同机器上耗时也不一样。所以公式里的加载时间是下限
  • 两边测试文件不一样大:4090 那台硬盘上没有大于 4GB 的模型文件,我临时写了个 4GB 的测试文件;矿卡那边读的是一个 10.7GB 的真实权重分片。文件大小会影响预读和缓存行为
  • Mac 那台我测到 6.13 GB/s,但清不了缓存(要管理员密码),只测了一次,很可能偏高。紧接着第二次读到 16.83,那显然是缓存了。所以 Mac 的数我只敢说「明显比另外两台快」,统一内存不走 PCIe,这个方向没问题

6. 下次给任务挑机器,按这个顺序

  1. 查每张卡的实际链路代数和宽度,在有负载时查,别看插槽上印的字。
  2. 用真实的加载函数量一次冷读、一次热读。冷热差就是硬盘的份,热读和纯链路带宽差得远,就是加载器在拖。
  3. 链路占比不到一半的机器,别指望换更宽的槽或更快的卡能加速加载,先换硬盘或者把权重留在内存里。
  4. 估算耗时写成「token 数 ÷ 解码速度 + 体积 ÷ 加载速度」,两项都要有。
  5. 两台机器一台解码快、一台加载快,用翻转点公式算出分界的 token 数,短于它的活给加载快的那台。
  6. 模型已经常驻在服务里的任务,加载项填 0,别重复算。
  7. 拿预测去跑真单,至少一单要跑完,把实际耗时和预测对一下,再信这张速度表。我自己这一步还欠着。