多个独立问题,该合并成一个请求还是拆开发?——LLM 并发处理的原理分析

当你有 5 个互不相关的问题,是打包成一条消息发给 LLM,还是同时发 5 个请求?哪种更快?

先说结论

拆成多个独立请求并行发送,几乎总是更快。

这不是直觉判断,而是由 LLM 的底层推理机制决定的。下面从原理层面逐步论证。

一、LLM 生成文本的基本机制:自回归

要理解这个问题,必须先搞清楚 LLM 是怎么”写字”的。

LLM(如 GPT-4、Claude)采用自回归生成(autoregressive generation):每次只生成一个 token,然后把这个 token 拼回输入,再生成下一个 token。循环往复,直到生成结束。

关键点:生成 N 个 token,就需要 N 次前向推理(forward pass)。

这意味着:
– 输出 100 个 token 的回答,需要 100 次推理步骤
– 输出 500 个 token 的回答,需要 500 次推理步骤
总输出长度直接决定了总耗时

二、合并请求:总输出量叠加,延迟线性增长

假设你有 5 个独立问题,每个问题的回答大约 200 tokens。

方案 A:合并成一个请求

你把 5 个问题塞进一条消息:

请分别回答以下问题:
1. xxx
2. xxx
3. xxx
4. xxx
5. xxx

LLM 需要生成的总输出 ≈ 5 × 200 = 1000 tokens。由于自回归机制,这 1000 个 token 是串行生成的——第 201 个 token 必须等前 200 个生成完才能开始。

总耗时 ≈ 1000 × 单 token 生成时间

而且还有额外开销:
– LLM 需要在回答之间维护上下文切换(”现在回答第 3 题”)
– 更长的 KV Cache 导致每一步 attention 计算量递增
– 实际输出往往超过 1000 tokens(格式化、过渡语句等)

三、拆分请求:并行推理,耗时取决于最慢的那个

方案 B:5 个问题拆成 5 个独立请求,同时发送

每个请求独立生成 ~200 tokens。如果服务端有足够的并发处理能力(现代 LLM 服务都有),这 5 个请求会被并行处理

总耗时 ≈ max(各请求耗时) ≈ 200 × 单 token 生成时间

对比一下:

方案 总输出 tokens 实际耗时(相对值)
合并一个请求 ~1000+ ~1000 步(串行)
拆分 5 个请求 每个 ~200 ~200 步(并行)

理论加速比 ≈ 5x(等于问题数量)。

四、为什么并行能成立?——服务端的 Continuous Batching

你可能会问:LLM 服务器不是也有容量限制吗?5 个请求同时来,不会排队吗?

现代 LLM 推理引擎(vLLM、TensorRT-LLM、TGI 等)都实现了 Continuous Batching(连续批处理):

  1. 多个请求共享同一次 GPU 矩阵运算:GPU 擅长的就是并行计算。把 5 个请求的 token 拼成一个 batch,一次 forward pass 就能同时为 5 个请求各生成一个 token。
  2. 动态调度:不同请求的输出长度不同,短的先结束,空出的 slot 立刻给新请求用。
  3. 吞吐量 vs 延迟的解耦:batch 越大,GPU 利用率越高,单位时间处理的 token 总量越多。

所以从服务端视角看:
– 5 个短请求并行 → GPU 做 5 路 batch 推理,每步同时产出 5 个 token
– 1 个长请求 → GPU 做 1 路推理,每步只产出 1 个 token

GPU 的并行计算能力在合并请求时被浪费了。

五、Prefill 阶段的差异

LLM 推理分两个阶段:

  1. Prefill(预填充):处理输入 prompt,计算所有输入 token 的 KV Cache。这一步可以并行处理所有输入 token,耗时与输入长度近似线性。
  2. Decode(解码):逐 token 生成输出。这一步是串行的。

合并请求时:
– Prefill 阶段:输入更长(5 个问题的描述拼在一起),prefill 时间更长
– Decode 阶段:输出更长,decode 时间更长

拆分请求时:
– 每个请求的 prefill 更短,且 5 个 prefill 可以并行或 pipeline 执行
– 每个请求的 decode 更短,且并行进行

两个阶段都是拆分更优。

六、还有一个容易忽略的因素:质量

除了速度,合并请求还有质量风险:

  • 注意力稀释:LLM 在一次生成中处理多个不相关任务时,对每个任务的”专注度”下降。研究表明,prompt 中无关信息越多,回答质量越低(Lost in the Middle 现象)。
  • 格式混乱:5 个问题的回答容易出现编号错乱、遗漏、答非所问。
  • 错误传播:如果第 2 个问题的回答出了问题,LLM 可能在后续回答中受到干扰(自回归的”惯性”)。

拆分请求则完全隔离了上下文,每个问题都能获得 LLM 的”全部注意力”。

七、什么时候合并反而更好?

公平起见,有少数场景合并可能更合适:

  1. 问题之间有隐含关联:虽然你认为独立,但 LLM 如果看到全貌可能给出更一致的回答(比如同一份报告的不同章节)。
  2. API 调用有严格的 rate limit:如果你的 API 配额是每分钟 3 次请求,那 5 个问题只能合并。
  3. 网络延迟远大于生成时间:如果每次 API 调用的网络往返是 2 秒,而生成只要 0.5 秒,那拆分 5 次的网络开销(5 × 2s = 10s)可能超过合并的生成时间。但这种情况在实际中很少见——现代 API 的网络延迟通常在 100-300ms,远小于生成时间。
  4. 极短回答:如果每个问题只需要一两个词的回答,那 prefill 的开销占比更大,合并可以减少重复的 prefill 成本。

八、实际验证思路

如果你想自己验证,可以这样测:

import asyncio
import time
import aiohttp

async def ask_single(session, question):
    start = time.time()
    # 调用 LLM API
    resp = await session.post(API_URL, json={"prompt": question})
    result = await resp.json()
    return time.time() - start

async def benchmark():
    questions = ["问题1", "问题2", "问题3", "问题4", "问题5"]

    async with aiohttp.ClientSession() as session:
        # 方案 A:合并
        start = time.time()
        combined = "请分别回答:\n" + "\n".join(questions)
        await ask_single(session, combined)
        time_combined = time.time() - start

        # 方案 B:并行
        start = time.time()
        await asyncio.gather(*[ask_single(session, q) for q in questions])
        time_parallel = time.time() - start

    print(f"合并: {time_combined:.2f}s")
    print(f"并行: {time_parallel:.2f}s")
    print(f"加速比: {time_combined / time_parallel:.1f}x")

根据经验,5 个中等复杂度的独立问题,并行通常能获得 3-5x 的加速。

总结

维度 合并一个请求 拆分多个并行请求
生成速度 慢(串行输出所有答案) 快(并行生成,取最慢值)
GPU 利用率 低(单序列推理) 高(batch 并行推理)
回答质量 可能下降(注意力稀释) 更好(独立上下文)
API 调用数 1 次 N 次
适用场景 有 rate limit / 问题极短 问题独立且需要详细回答

核心原理一句话总结:LLM 的自回归机制决定了输出是串行的,合并请求 = 强制串行所有输出;拆分请求 = 利用服务端并行能力同时生成多个输出。独立问题拆开发,是用空间(并发 slot)换时间的经典策略。

Comments

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注