面试日记 第 26 天

面试官把我带到白板前,递了支马克笔。

“做个估算题。”她说,“集群吞吐 1000 tokens/s,1000 个用户并发,平均每人分到多少?”

这题不用想。我转身在白板上写下:1000 ÷ 1000 = 1 token/s。写完还在末尾点了个自信的句号。

“就这速度,用户不骂街吗?”她问。

“所以会限流、会排队啊。”我说,“算力就这么多,要么加卡,要么忍着。”

她没评价,从桌上拿起手机翻了两下,走到白板旁边,把屏幕举在我的算式旁边。

“DeepSeek V4 灰度,半夜半价。”她说,“照你白板上这个算法,它半夜的单价早就亏穿了。可它敢这么定价,你说为什么?”

我盯着白板上那个“1”,第一次觉得这个式子有点不对劲。

她把原题抛了出来:

如果一个 GPU 集群的 LLM 处理能力为 1000 tokens,那 1000 个用户同时并发访问,响应给每个用户的性能只有 1 tokens 吗?怎么分析性能瓶颈?

我握着笔站了几秒,抬手把那个等式擦了。

“我算错了。”我说,“它不是把算力切成一千份。LLM 推理是靠批处理把多个请求打包一起算——GPU 一次 forward 给 batch 里所有请求各算一个 token,100 个请求的 batch 和 1 个请求耗时差不多,吞吐直接翻几十倍。Continuous Batching 再让短请求生成完就退出、新请求立刻插进来,流水线不停。所以 1000 个用户并发,每个人实际体验到的可能是几十 token/s,不是 1。”

“那这跟半夜半价有什么关系?”她追问。

这一问,我反而彻底想通了。

“关系大了。推理服务的成本看的是吞吐利用率,不是单价。白天高峰,batch 塞得满满的,GPU 一点闲不下来;半夜请求少,batch 填不满,算力就那么空转着——反正电照跑、卡照热。低谷降价不是赔本,是把对价格敏感的任务引到半夜,把填不满的 batch 填满。多收一笔是一笔,边际成本几乎为零。”

她收回手机,靠回椅背。

“所以它不是做慈善,是清库存。”她说,“算力跟酒店房间一个道理,今晚卖不出去就作废。峰谷定价不是营销话术,是调度策略的延伸——这层想不明白,成本题都答不到点子上。”

回去我把白板照片翻出来又看了一遍。以前看价格战只看谁烧钱狠,现在得先看谁的 batch 填得满。

回答重点

不会简单平均成每人 1 token/s,实际上每个用户看到的响应速度可能是几十 token/s。

LLM 推理不是把算力切成 1000 份分给 1000 个用户,而是靠批处理把多个请求打包到一起算。GPU 擅长的就是并行计算,100 个请求打成一个 batch,计算耗时跟处理单个请求差不多,吞吐量直接翻几十倍。

假设每次批处理包含 100 个用户的请求,每个用户请求 10 tokens,那 1000 个用户分 10 批就处理完了。单用户实际体验到的速度是 10 tokens/s,不是 1 token/s。

实际响应速度取决于三个核心因素:请求的 token 长度、batch 大小策略、排队调度机制。

扩展知识

请求聚合与调度机制

LLM 推理有个特点:一次 forward 只生成 1 个 token,然后循环生成下一个。1000 个用户同时来请求,GPU 不是一个一个处理,而是每轮同时算这 1000 个请求的下一个 token。

这就需要一个请求聚合器来协调:

1)把同时到达的请求按 token 长度打包,比如用户 A 发 2 个 token、用户 B 发 4 个、用户 C 发 6 个,聚合器会把短的补齐到相同长度,打成一个矩阵扔给 GPU 一次算完

2)通常有个聚合窗口,比如每 5ms 或者攒够 32 个请求就发一批,跟公交车发车一个道理:不等人就浪费座位,等太久乘客就骂娘

3)控制粒度是 token-level batching,同一时刻只处理所有请求的当前 token,算完再一起推进到下一个

动态调度与优先级

请求进来不会立刻推理,先进一个异步队列,调度器根据策略决定谁先算:

1)优先级策略:付费用户优先、重试请求优先、token 少的先算完让出位置

2)负载均衡:GPU 显存吃紧时减小 batch size,空闲时加大 batch 塞更多请求

3)动态退场:已经生成完的请求退出 batch,新请求插进来,整个过程是一条流水线

实际场景分析

拿一个聊天机器人平台举例:1000 个用户并发,请求平均 20 tokens,GPU 最大 batch 128,吞吐 1000 tokens/s。

调度可能是这样跑的:

1)每 10ms 打一批,聚合 50~100 个请求

2)所有请求每生成一个 token 就进入下一轮调度

3)pipeline 里同时跑着多个 batch,每个 batch 装的是不同用户的不同 token

最终每个用户实际体验到的响应速度是几十 token/s,远比简单除法算出来的 1 token/s 快。

性能瓶颈分析思路

遇到吞吐上不去或者延迟高,从这几个方向排查:

1)显存瓶颈:KV Cache 占用太大,batch size 上不去,看 nvidia-smi 的显存占用

2)计算瓶颈:GPU 利用率拉满但吞吐还是低,说明模型太大或者 batch 太小没喂饱 GPU

3)调度瓶颈:队列堆积严重,调度器跟不上请求速度,看请求排队时间

4)网络瓶颈:分布式推理场景下,节点间通信成瓶颈,看 NCCL 的耗时占比

vLLM、TensorRT-LLM 这些推理框架都有 metrics 接口,可以直接看 batch size 分布、排队延迟、吞吐曲线,定位瓶颈点。

Java 模拟示例

用 Java 模拟并发请求和资源排队机制:

ExecutorService executor = Executors.newFixedThreadPool(100);
Semaphore gpuTokens = new Semaphore(1000); // 模拟 1000 token/s 的能力

for (int i = 0; i < 1000; i++) {
    executor.submit(() -> {
        try {
            if (gpuTokens.tryAcquire(10, 1, TimeUnit.SECONDS)) {
                System.out.println("Token allocated to user: " + Thread.currentThread().getName());
                Thread.sleep(100); // 模拟推理延迟
                gpuTokens.release(10);
            } else {
                System.out.println("Timeout, user dropped.");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });
}

这段代码用 Semaphore 模拟 GPU 的 token 处理能力,每个用户请求 10 个 token,超时就丢弃。实际生产环境的调度比这复杂得多,但核心思想一样:资源有限,靠排队和批处理来提升整体吞吐。

面试官追问

追问:vLLM 的 PagedAttention 机制是怎么优化显存利用率的?

回答:传统做法是给每个请求预分配最大长度的 KV Cache,比如最大 2048 tokens 就分配 2048 的显存,但大多数请求用不完,显存浪费严重。PagedAttention 借鉴操作系统的分页思想,把 KV Cache 切成固定大小的 block,按需分配。请求来了先给一个 block,用完再分配下一个,就跟虚拟内存一样按需加载。这样显存利用率能从 20~30% 提升到 90% 以上,同样的显存能塞更多请求,吞吐直接翻几倍。

追问:Continuous Batching 和传统的 Static Batching 有什么区别?

回答:Static Batching 是攒一批请求,等所有请求都生成完才处理下一批。问题是短请求早就生成完了还得等长请求,GPU 干等着浪费算力。Continuous Batching 是动态调度,短请求生成完就退出 batch,新请求马上插进来,整个过程像流水线一样不停转。vLLM、TensorRT-LLM 都用的这种方式,吞吐能比 Static Batching 高 2~3 倍。

追问:推理服务的 First Token Latency 和 Time Per Output Token 怎么分别优化?

回答:First Token Latency 是首 token 延迟,主要卡在 prefill 阶段,整个 prompt 要一次性过一遍模型。优化方向是 prompt 压缩、KV Cache 预计算、prefill 和 decode 分离部署。Time Per Output Token 是后续每个 token 的生成耗时,主要看 decode 阶段的效率,优化方向是加大 batch size、用 speculative decoding 一次预测多个 token、量化降低计算量。两个指标侧重点不一样,First Token 影响用户体感的响应速度,TPOT 影响整体吞吐。

追问:模型量化对推理性能有什么影响?INT8 和 FP16 怎么选?

回答:量化就是降低权重和激活值的精度,FP16 比 FP32 快一倍左右,INT8 比 FP16 又快一倍左右。但精度损失也是真实存在的,INT8 量化后模型效果可能会掉几个点。选型看场景:对精度要求高的用 FP16,追求极致吞吐且能接受一点效果损失的用 INT8。还有个折中方案是混合精度,attention 层用 FP16 保精度,FFN 层用 INT8 提速度。AWQ、GPTQ 这些量化方案在保精度上做了很多优化,实际效果损失比朴素 INT8 小很多。


后来我又看了眼 DeepSeek 的定价页。低谷时段那栏价格,跟酒店淡季促销的页面排布几乎一模一样——本质确实是一回事:会过期的库存,打折也要卖出去。差别只是酒店卖的是房间,它卖的是 batch 里空着的座位。

这道题容易把"总算力除以人数"当成直觉答案,那是把 GPU 当成了单线程服务员。面试官真正想听的是批处理、Continuous Batching 和调度策略怎么把吞吐翻几十倍,以及瓶颈排查时先看显存、再看计算、最后看队列的顺序。完整题解和更多追问已经整理在面试鸭,准备大模型应用或推理优化方向的同学,建议把这题和 KV Cache、量化相关的一组题连着过一遍。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐