Concurrency Sweep · RTX 4090
固定 1024 输入 / 256 输出,并发从 1 扫到 64,共 1,332 条请求全部成功。 这条曲线回答一个问题:这张卡上,吞吐要涨到多少才开始拿延迟去换。
吞吐一路涨到 C=64 都没有真正饱和,但"每多给一个并发槽换来多少 token/s"从 C=8 起就掉得很快。 真正划线的不是吞吐曲线,是 ITL 的尾部 —— 它在 C=8 那一点突然裂开。
推荐运行点
C = 16 · 683 tok/s
吞吐达到单流的 11.1 倍,p50 ITL 仍只有 17.4 ms(比单流慢 9%),p99 TTFT 1.39 秒。 再往上走,C=32 多拿 51% 吞吐,代价是 p99 TTFT 翻一倍到 2.85 秒。
单流实测 / 理论上限
92.7 %
61.5 vs 66.3 tok/s,decode 已贴住显存带宽
C=64 吞吐增益
22.8 ×
用 64 倍并发换来,扩展效率 35.7%
ITL p99 / p50 最大比
10.3 ×
C=16 处,均值完全看不见这个裂口
C=64 时 KV 池占用
94.3 %
真实并发天花板只有 67 条,不是 128
横轴是实际产出的 token/s,纵轴是 p99 首字延迟(对数)。曲线越往右越贵 —— 从 C=1 走到 C=16 花了 1.28 秒 p99 TTFT 买到 11 倍吞吐;从 C=32 走到 C=64 只多买到 36% 吞吐,却付出了 2.9 秒。
吞吐 – 延迟权衡曲线
灰色横带是三档常见 SLO。p50 与 p99 在 C=8 之后急剧分离,说明高并发下的慢请求 不是整体变慢,而是少数请求被排到了后面。
双对数坐标下,理想线性扩展是一条直线。实测曲线从 C=4 开始明显偏离, 到 C=64 时只剩理想值的 35.7%。
扩展效率:实测 vs 理想线性
点旁标注为该点的扩展效率。
这是判断拐点最直接的量。第一个并发槽带来 56.6 tok/s,到 C=32→64 那一段, 每个槽只剩 11.6 tok/s —— 边际收益衰减到五分之一。
边际吞吐收益(每新增一个并发槽)
柱高 = 该区间内新增吞吐 ÷ 新增并发数。虚线为 C=1 的基准值 61.5 tok/s。
这是整轮压测里最值得看的一张图,也是"P99 ITL 才暴露抖动"这句话最干净的实证。 p50 ITL 从 C=1 到 C=64 只从 15.9 ms 涨到 22.7 ms —— 涨了 43%,温和得看不出问题。 但 p99 ITL 在 C=8 那一点从 17.4 ms 直接跳到 99.6 ms,一步跳了 5.7 倍。
ITL 分位数分裂(对数纵轴)
阴影区是 p50 与 p99 之间的跨度。C=4 时几乎看不见,C=8 之后张开成一个数量级。 注意均值线始终贴着 p50 与 p99 之间 —— 它两头都不代表。
这不是抢占
服务端日志里 preempt 出现 0 次,Waiting 队列最长只有 3 条。
所以尾部延迟不是 KV 不够导致的请求被踢出重算。
真正的原因是 enable_chunked_prefill=True(日志确认):
新请求的 prefill 被切块后混进正在解码的批次。
对已经在解码的请求来说,那一步要等 prefill 块算完,于是出现一个 100–200 ms 的停顿。
C=64 时 p90 ITL 已达 188 ms —— 超过一成的 token 间隔卡在别人的 prefill 上。
E2EL 构成:首字等待 vs 解码耗时(p50)
解码段从 4.06 秒涨到 10.92 秒,是 E2EL 增长的主体。TTFT 虽然 p99 很难看, 但在中位数上始终只占总时长的 3%–7%。
服务端启动参数写的是 --max-num-seqs 128,但那只是名义上限。
实际能同时跑多少条,由 KV cache 池除以单请求占用决定。
# 服务端日志实测 Available KV cache memory: 4.64 GiB GPU KV cache size: 86,896 tokens # 单请求占用 = 输入 + 输出 1024 + 256 = 1,280 tokens # 真实并发上限 86,896 ÷ 1,280 = 67.9 条
各并发点的 KV cache 池占用
C=64 需要 81,920 tokens,占池 94.3% —— 与服务端观测到的峰值 92.0% 吻合。 被注释掉的 C=128 需要 163,840 tokens,是池子的 1.9 倍,根本跑不起来。
有 1.48 GiB 显存没被用上
显存账:权重 + non-torch 15.49 GiB,峰值激活 1.04 GiB,CUDA Graph 0.34 GiB,
KV 池 4.64 GiB,合计 21.51 GiB。而 --gpu-memory-utilization 0.90
给出的预算是 21.17 GiB,卡上总共有 23.52 GiB。
vLLM 自己在日志里给了建议:--kv-cache-memory=6569114112(6.12 GiB)。
照做能把池子扩到约 114,600 tokens,并发上限从 67 提到 89,
而且不需要动 gpu-memory-utilization。
| 并发 | 请求数 | 耗时 s | 输出 tok/s | 效率 | TTFT p50 | TTFT p99 | TPOT p50 | ITL p50 | ITL p99 | E2EL p50 | KV 占用 |
|---|
| 如果 SLO 是 | 选并发 | 能拿到吞吐 | 实际 p99 TTFT | p50 ITL |
|---|
--kv-cache-memory=6569114112,
池子 4.64 → 6.12 GiB,并发上限 67 → 89。这是唯一一个纯赚不赔的改动。--max-num-batched-tokens。
调小 → prefill 块更细 → ITL 尖峰更矮,但 TTFT 会变差。这是一个明确的对换,
取决于你的场景是流式聊天(在意 ITL)还是批量任务(在意 TTFT)。--random-range-ratio 0。
本轮 total_input_tokens 正好是 1024 × num_prompts,说明这个版本默认是定长,
结果可信;但换台机器或换个版本就不一定了。