16 KiB
你这个批评是对的。 上一个版本把问题讲成了“单请求 service time + 泛泛 queueing”,但对 vLLM 这类 continuous batching / bulk-service 系统来说,这样建模不够锋利。
先把问题修正:正确的服务对象不是 request,而是 batch
对 TTFT 来说,真正被执行的是 prefill batch,不是单个请求。 因此更准确的建模方式是:
- 先定义 batch runtime
T_b(t) - 再定义 request 的 TTFT 是“排队等到自己所在 batch 被执行”加上“自己所在 batch 的执行时间”
而不是先写一个模糊的 $S_{\text{comp}} + S_{\text{comm}} + S_{\text{sched}}$。
一、最小而精确的建模框架
设:
- 总 GPU 数固定为
G - TP 为
t - 则可部署 replica 数为
m_t = \frac{G}{t}
对某个 replica,上面的第 b 个 prefill batch 记为 $\mathcal{B}_b$。
对 batch 中每个请求 $i$:
- prompt length 为
x_i - 该 batch 的总 prefill token 数为
Z_b = \sum_{i \in \mathcal{B}_b} x_i
二、batch runtime T_b(t) 应该怎么写
2.1 总式子
更准确地,应该写成:
T_b(t) = T_b^{\text{comp}}(t) + T_b^{\text{comm}}(t) + T_b^{\text{rt}}(t)
其中:
- $T_b^{\text{comp}}(t)$:真正算子计算时间
- $T_b^{\text{comm}}(t)$:TP collective 通信时间
- $T_b^{\text{rt}}(t)$:runtime 固定开销,例如 launch gap、executor 调度、图切换、kernel 边界等
这里我故意不用模糊的 $S_{\text{sched}}$,而把它收敛为 runtime overhead residual。 因为对这类系统,“调度”大量体现为 batch 之间的时间缝隙和执行边界成本,而不是某个独立的物理项。
三、T_b^{\text{comp}}(t) 到底怎么算
3.1 prefill 的计算量不是只和 \sum x_i 成正比
对 decoder-only Transformer,一个 prompt length 为 x 的请求,其 prefill 计算量可以抽象成:
F(x) = a x d^2 + b x^2 d
其中:
d是 hidden sizea x d^2对应 dense 部分:QKV 投影、O 投影、MLP 等b x^2 d对应 self-attention 部分a,b吸收了层数 $L$、MLP expansion ratio、head 结构等常数
因此,一个 batch 的总 FLOPs 是:
F_b = \sum_{i \in \mathcal{B}*b} F(x_i)
= a d^2 \sum*{i \in \mathcal{B}*b} x_i + b d \sum*{i \in \mathcal{B}_b} x_i^2
如果把层数 L 显式写出来,则是:
F_b = L \left( a' d^2 \sum_{i \in \mathcal{B}*b} x_i + b' d \sum*{i \in \mathcal{B}_b} x_i^2 \right)
这比“S_{\text{comp}} 是计算项”要具体得多,因为这里直接告诉你:
prefill batch 的成本对长度分布是凸的,关键不是只有总 token 数 $Z_b$,而是还取决于 $\sum x_i^2$。
3.2 一个非常关键的结论:length variance 直接进入 batch cost
因为:
\sum_{i \in \mathcal{B}_b} x_i^2
= n_b \left( \bar{x}_b^2 + \operatorname{Var}_b(x) \right)
其中:
n_b = |\mathcal{B}_b|\bar{x}_b是 batch 内平均 prompt length\operatorname{Var}_b(x)是 batch 内长度方差
所以在固定 n_b 和固定平均长度 \bar{x}_b 下:
- 方差越大
\sum x_i^2越大- prefill compute cost 越高
这就是一个很强的、可计算的 insight:
heterogeneity 不只是“调度难”,而是直接提高了 attention 的物理计算量。
这也解释了为什么 coder window 往往更敏感:
它通常不是只有更高平均长度,而是更高的 \operatorname{Var}(x) 和更高的长尾。
3.3 把 FLOPs 映射成计算时间:roofline 形式
给定 batch b 和 TP=$t$,其计算时间可以写成:
T_b^{\text{comp}}(t) = \max\{\frac{F_b}{\Pi_t}, \frac{Q_b}{B_t}\}
其中:
\Pi_t是 TP=t时该 instance 的有效算力Q_b是这个 batch 的总 memory trafficB_t是 TP=t时该 instance 的有效 HBM 带宽
再进一步写:
\Pi_t = t \Pi_1 \eta_t^{\text{comp}}
B_t = t B_1 \eta_t^{\text{mem}}
其中:
\eta_t^{\text{comp}} \le 1是 TP 扩大后由于 GEMM shape 变化、kernel utilization 降低带来的效率损失\eta_t^{\text{mem}} \le 1是内存系统扩展效率
于是:
T_b^{\text{comp}}(t)
=
\max
\{
\frac{F_b}{t \Pi_1 \eta_t^{\text{comp}}},
\frac{Q_b}{t B_1 \eta_t^{\text{mem}}}
\}
这已经比“S_{\text{comp}} 会随 TP 降低”精确很多了。
它明确说明:
- 理想情况下,compute/memory 时间会按
1/t降 - 但实际只能按
1/(t \eta_t)降 - 而
\eta_t通常随t变大而下降,所以是 次线性加速
四、T_b^{\text{comm}}(t) 到底怎么算
4.1 TP 的通信本质是 activation collective
对标准 tensor parallel,每层通常会有若干个 activation 同步。
如果用 ring all-reduce 近似,大小为 n bytes 的一次 collective 时间是:
T_{\text{AR}}(n,t)
=
2 (t-1) \alpha
+
2 \frac{t-1}{t} \frac{n}{\beta}
其中:
\alpha是每 hop latency\beta是链路带宽
如果每层平均有 k 次这样的 collective,且每次 payload 与 batch activation 大小成正比:
n_b \approx q d Z_b
其中 q 是 dtype bytes,那么:
T_b^{\text{comm}}(t)
\approx
L k
\left(
2 (t-1)\alpha
+
2 \frac{t-1}{t} \frac{q d Z_b}{\beta}
\right)
这就是你要的更具体形式。
4.2 这个式子带来的核心含义
它说明 TP 通信开销有两个部分:
固定项
2 (t-1)\alpha
这是 latency-driven 的,batch 小时特别痛。
线性 payload 项
2 \frac{t-1}{t} \frac{q d Z_b}{\beta}
这是 batch token 数越大越重。
所以:
- 低负载、小 batch 时,通信固定项不一定主导,但也不会消失
- 高负载、大 batch 时,payload-driven 通信会上升
- 更重要的是:即使 compute 时间下降,communication 不会按
1/t同步下降
这也是 TP 速度提升无法线性的根本原因。
五、把三项合起来:一个真正可用的 T_b(t)
综合起来,一个 batch 的执行时间可以写成:
T_b(t)
======
\max
\{
\frac{
L ( a' d^2 \sum_i x_i + b' d \sum_i x_i^2 )
}{
t \Pi_1 \eta_t^{\text{comp}}
},
\frac{Q_b}{t B_1 \eta_t^{\text{mem}}}
\}
+
L k
(
2 (t-1)\alpha
+
2 \frac{t-1}{t} \frac{q d Z_b}{\beta}
)
+
T_b^{\text{rt}}(t)
这已经是一个相当具体的 mechanistic model 了。
六、TTFT 应该怎么精确定义,而不是模糊说 W_q + E[S_t]
6.1 request 级别的精确定义
对请求 $i$,设它被分配到某个 replica,并最终进入 prefill batch $b(i)$。 它的 TTFT 精确写成:
\mathrm{TTFT}_i(t)
==================
W_{q,i}(t) + T_{b(i)}(t)
其中:
W_{q,i}(t)是它在自己 batch 真正开始执行前等待的时间T_{b(i)}(t)是它所在 batch 的执行时间
而 W_{q,i}(t) 又可以精确分解为:
W_{q,i}(t)
==========
R_i(t) + \sum_{u \in \mathcal{H}_i} T_u(t)
其中:
R_i(t)是请求到达时当前正在跑的那个 batch 的 residual runtime\mathcal{H}_i是它前面还排着的 prefill batches 集合
这个式子是对的,而且比“queueing delay”四个字更有结构。
6.2 为什么上次写的 E[S_t] 不够准确
因为对 continuous batching 系统,“request 的 service time”不是一个天然的一维随机变量。
更自然的随机变量其实是 batch runtime $T_b(t)$。 request 的 TTFT 是:
- 一个 residual batch
- 加上若干个完整 batch
- 再加上自己的 batch
所以更对的期望写法是围绕 $T_b(t)$,而不是围绕某个抽象的 $S_t$。
6.3 如果一定要写平均 queueing term,应该怎么写
在稳态下,如果把 batch 看成 renewal process,那么 residual life 的精确均值是:
\mathbb{E}[R_t]
===============
\frac{\mathbb{E}[T_b(t)^2]}{2 \mathbb{E}[T_b(t)]}
这比“queueing increases with variance”更具体,因为它明确告诉你:
batch runtime 的二阶矩直接进入等待时间。
如果再做一个独立性近似,则:
\mathbb{E}[W_q(t)]
\approx
\frac{\mathbb{E}[T_b(t)^2]}{2 \mathbb{E}[T_b(t)]}
+
\mathbb{E}[|\mathcal{H}|] , \mathbb{E}[T_b(t)]
其中 |\mathcal{H}| 是到达时前方排着的 batch 数。
这就已经比之前的 $E[S_t]$、W_q(t,w) 精确得多了。
七、真正关键的量不是 $E[S_t]$,而是 cluster capacity
如果你想分析“为什么高负载时小 TP 更好”,最关键的不是某个 request 的平均 service time,而是:
固定总 GPU 数下,cluster 能提供的总服务能力到底是多少。
7.1 单 instance 的 batch token throughput
对 workload window $w$,定义 TP=t 时单 instance 的平均 prefill token throughput 为:
\mu_{t,w}
=========
\mathbb{E}
\left[
\frac{Z_b}{T_b(t)}
\middle| w
\right]
这里:
Z_b是 batch token 数T_b(t)是 batch runtime
这是一个比 “$E[S_t]$” 更自然的核心量。
7.2 cluster 总 capacity
因为总 replica 数是 $m_t = G/t$,所以 cluster 级别的总 prefill capacity 是:
\Lambda_{t,w}
=============
\frac{G}{t} , \mu_{t,w}
这个式子非常关键。 它说明:TP 的 tradeoff 不是抽象的,而是直接落在
- 单 instance throughput:
\mu_{t,w} - replica 数:
G/t
两者的乘积上。
八、为什么高负载下大 TP 很难赢:一个几乎是定理级别的结论
比较 TP=t 和 TP=1 的 cluster capacity:
\frac{\Lambda_{t,w}}{\Lambda_{1,w}}
===================================
\frac{\mu_{t,w}}{t \mu_{1,w}}
因此,大 TP 只有在下面这个条件成立时,才能在固定总 GPU 数下提升 cluster capacity:
\mu_{t,w} > t \mu_{1,w}
也就是说,单 instance throughput 必须超过线性扩展。
但这是非常苛刻的,因为:
- compute 只能理想到线性
- 实际有
\eta_t^{\text{comp}} < 1 - 还有额外的
T_b^{\text{comm}}(t) - 还有 runtime overhead
所以在稳态下,通常只能有:
\mu_{t,w} < t \mu_{1,w}
从而:
\Lambda_{t,w} \le \Lambda_{1,w}
这就是为什么在固定总 GPU 数下:
大 TP 几乎不可能提升饱和状态下的 cluster total capacity。
它能提升的是:
- 单请求 latency
- 单 instance 的 service time
但它通常不能提升总集群容量。
这就是你图里最核心的 principle。
九、为什么低负载时 TP4 更好,但高负载时 TP1/TP2 更好
9.1 低负载:系统是 arrival-limited
观测到的 goodput 近似为:
g_{t,w}^{\text{obs}}
\approx
\frac{1}{G}
\min
\left{
\lambda_w^{\text{good}},
\Lambda_{t,w}
\right}
其中:
\lambda_w^{\text{good}}是外部 offered good-token arrival rate\Lambda_{t,w}是系统 capacity
当低负载时:
\lambda_w^{\text{good}} \ll \Lambda_{t,w}
对所有 TP 都成立,所以:
g_{t,w}^{\text{obs}}
\approx
\frac{\lambda_w^{\text{good}}}{G}
因此不同 TP 的 observed goodput/GPU 看起来几乎一样。
这就解释了你在 time_scale=0.5 看到的现象:
- goodput/GPU 差异很小
- 但 TP4 latency 更好
因为这时真正显现出来的是:
T_b(4) < T_b(1)
而 capacity 差异被 arrival-limited 掩盖了。
9.2 高负载:系统开始 capacity-limited
当:
\lambda_w^{\text{good}} \approx \Lambda_{t,w}
或者超过它时,系统进入高利用率区。 此时:
- observed goodput 开始接近 capacity
- queueing term 开始爆炸
因为利用率可以写成:
\rho_{t,w}
==========
\frac{\lambda_w^{\text{good}}}{\Lambda_{t,w}}
而大 TP 通常让 \Lambda_{t,w} 下降,所以会让 \rho_{t,w} 上升。
一旦 \rho_{t,w} 接近 $1$,你就会看到:
- queueing delay 急剧上升
- p95 TTFT 急剧恶化
- SLO pass 数下降
- goodput/GPU 反而输给小 TP
这就解释了为什么高负载下 TP1/TP2 更优。
十、为什么 coder 比 chat 更容易出现这个现象
这可以从两个层面解释。
10.1 batch cost 的凸性更强地惩罚高方差长度分布
前面已经有:
F_b
===
a d^2 \sum_i x_i + b d \sum_i x_i^2
而:
\sum_i x_i^2
============
n_b \left( \bar{x}_b^2 + \operatorname{Var}_b(x) \right)
所以 coder 如果具有更高的:
- long tail
- length variance
- long fraction
那么 batch runtime 就会更高,而且不是线性地更高。
10.2 queueing tail 还会额外放大 runtime variance
你真正关心的是 p95 TTFT。
而 p95 受的不仅是 $\mathbb{E}[T_b]$,还受 \mathbb{E}[T_b^2] 甚至更高阶尾部分布影响。
如果 coder 的 batch runtime 分布更 heavy-tail,那么:
\mathbb{E}[R_t]
===============
\frac{\mathbb{E}[T_b(t)^2]}{2 \mathbb{E}[T_b(t)]}
也会更大。
所以 coder 更早进入:
- residual time 大
- queue 深
- p95 爆掉
的 regime。
十一、因此你应该怎么总结这条 principle
你现在最该写的不是:
负载越大,TP 越小越好。
而应该写成下面这个更强、更准确的形式:
Principle
在固定总 GPU 数下,TP 同时影响两个不同层面的量:
-
单 instance 的 batch runtime $T_b(t)$ 更大的 TP 往往降低 compute time,因此改善低负载下的 TTFT。
-
集群总 capacity $\Lambda_{t,w} = \frac{G}{t}\mu_{t,w}$ 由于 TP 扩展通常只有次线性速度提升,且引入额外 collective 开销,因此
\mu_{t,w}很难超过线性增长,导致 cluster capacity 通常随 TP 增大而不升反降。
因此:
- 在低负载区,系统是 arrival-limited,capacity 差异被隐藏,TTFT 主要由
T_b(t)决定,所以较大 TP 更优。 - 在高负载区,系统是 capacity- / queueing-limited,
\Lambda_{t,w}和\rho_{t,w}主导表现,因此较小或中等 TP 更优。
十二、把你图里的 observation 严格落到这个模型上
你现在的图,其实支持的是下面这句话:
低载区
在 sampled low-load windows 上,
\lambda_w^{\text{good}} \ll \Lambda_{t,w}, \quad \forall t \in {1,2,4}
因此 observed goodput/GPU 相近;同时
T_b(4) < T_b(1)
所以 TP4 的 p95 TTFT 更好。
高载区
随着 time_scale 增大,\lambda_w^{\text{good}} 被放大,系统开始逼近:
\lambda_w^{\text{good}} \approx \Lambda_{t,w}
而由于通常有:
\Lambda_{4,w} < \Lambda_{2,w} \lesssim \Lambda_{1,w}
或者至少:
\Lambda_{4,w} < \Lambda_{2,w}
所以 TP4 更早进入高利用率区,queueing tail 更早爆炸,于是:
- goodput/GPU 开始输给小 TP
- p95 TTFT 恶化更明显
这就是你观察到的 phase change。
十三、如果你要把这个模型真正用于预测,下一步该怎么做
你现在可以不再泛泛地说“workload affects TP”,而是直接拟合下面这些量。
需要拟合的量
| 量 | 含义 | 如何估计 |
|---|---|---|
a,b |
prefill FLOP 模型常数 | 用 isolation microbench 拟合 batch runtime 对 \sum x_i 和 \sum x_i^2 的系数 |
\eta_t^{\text{comp}} |
TP 计算扩展效率 | 对固定 batch 组合测 T_b^{\text{comp}} 反推 |
\alpha,\beta |
collective latency / bandwidth 参数 | 用 NCCL microbench 或 server log 中 collective 时间拟合 |
T_b^{\text{rt}}(t) |
runtime residual | 用实测 T_b - T_b^{\text{comp}} - T_b^{\text{comm}} 得到 |
\mu_{t,w} |
单 instance token throughput | 直接从 trace replay 实测 batch token / runtime 统计 |
\Lambda_{t,w} |
cluster capacity | 用 \frac{G}{t}\mu_{t,w} 计算 |
十四、最本质的一句话
这条 principle 的本质其实可以压缩成一句话:
大 TP 优化的是“单个 batch 跑多快”,小 TP 优化的是“固定 GPU 预算下整个 cluster 同时能接多少活”。
在低负载时,前者主导。 在高负载时,后者主导。
如果你愿意,我下一条可以继续直接把这个模型再往前推一步,给你一版可写进论文的小节,包括:
- 精确定义
- Proposition
- 证明思路
- 为什么会出现 TP4
\rightarrowTP2\rightarrowTP1 的 regime shift。