位置:首页 > 热点资讯 > AI智能体增多导致系统变慢的原因

AI智能体增多导致系统变慢的原因

时间:2026-07-24  |  作者:318050  |  阅读:0

你正在构建一个基于 LLM 的产品。你正在攻克一个之前不可能实现的挑战,而现在,借助 LLM,它突然变得触手可及。你部署了最初的几个智能体到系统中,一切看起来都正常。客户很满意。响应速度虽然不是最快的——LLM 确实天生缓慢——但还在可控范围内。

为什么增加更多 AI 智能体导致我们的系统变慢

当你开始部署越来越多的智能体时,奇怪的现象开始出现。逐渐地,延迟开始增加。起初,你责怪 LLM 提供商。也许是新的 OpenAI 模型更慢了。也许是 Anthropic 正承受着沉重负载。LLM 天生就慢,对吧?

然后,超时错误开始出现。当你深入查看日志和指标时,发现有些地方不对劲。OpenAI 声称请求完成得非常快,而你的智能体却花费了异常长的时间来响应。

“OpenAI 一定在骗我们!”

接着,痛苦的觉醒时刻到来了。

问题不在 OpenAI,而在于我们的代码。

在 Planck,团队恰好经历了这样的转变。随着智能体生态系统的增长,响应时间也随之增加。这就是发现真正瓶颈并解决它的故事。

如果你正在将 LLM 智能体部署到生产环境,并想知道它们将如何扩展,或者你是一名 AI 工程师,又或者你只是喜欢挑战异步系统设计问题,请继续往下读。

架构设定

在 Planck,多个 LLM 智能体在生产环境中运行。它们都由一个单一服务提供服务,这意味着一个请求——无论是通过 HTTP 还是工作队列——会同时触发所有智能体。每个智能体又会向其自己的子智能体发起数十次调用。

以下是 Python 代码的简化版本:

import asyncio
import aiohttp

NUM_AGENTS = 5  # agents triggered by one request
CALLS_PER_AGENT = 30  # sub-agent LLM calls each agent makes

async def call_sub_agent(session: aiohttp.ClientSession) -> dict:
    """A single sub-agent step = one LLM call (io-bound)."""
    payload = {
        "model": "your-llm-model",
        "messages": [{"role": "user", "content": "some fake prompt"}],
    }
    async with session.post("llm_url", json=payload) as resp:
        return await resp.json()

async def run_agent(session: aiohttp.ClientSession, agent_id: int) -> list[dict]:
    """One agent fans out to dozens of sub-agent LLM calls at once."""
    return await asyncio.gather(*(call_sub_agent(session) for _ in range(CALLS_PER_AGENT)))

async def handle_request() -> list[list[dict]]:
    """A single request triggers every agent simultaneously."""
    async with aiohttp.ClientSession() as session:
        return await asyncio.gather(*(run_agent(session, agent_id) for agent_id in range(NUM_AGENTS)))

乍看之下,这个架构很理想。几乎所有工作都是 I/O 密集型,因此使用 Python 的异步生态可以让每个智能体并发执行。所以,一个请求的延迟应该由最慢的 LLM 调用主导,而不是由智能体总数决定。

至少,这是预期的情况。

为了测试这一点,我们使用了一个本地的 FastAPI 服务器。不用担心,我们验证过它响应很快,不是瓶颈。
我们在服务器端点中使用了小范围的随机 async sleep 来模拟 LLM 响应的时间。同时发送并接收了较大的数据负载,这在 LLM 工作流中很常见。

结果可能因负载大小、async sleep 范围以及每个智能体的子智能体数量而有所不同。因此,不要关注绝对数值,而应关注整体趋势

(一张图:响应时间随智能体数量增加而变化的曲线,作者自绘)

深入分析

为什么会这样?代码正确地使用了 async。所有智能体和子智能体本应同时运行,因此理论上,运行一个智能体与运行 10,000 个智能体几乎没有区别

在调试模式下运行代码时,出现了事件循环延迟警告。这意味着任务已经完成(LLM 返回了结果),但事件循环无法获取一个工作协程来处理它,因为事件循环正忙于其他事情。在极端情况下,这会导致生产环境中出现的超时错误。

WARNING:root: event-loop lag: 230 ms (loop was busy, not waiting)
WARNING:root: event-loop lag: 137 ms (loop was busy, not waiting)
WARNING:root: event-loop lag: 146 ms (loop was busy, not waiting)
WARNING:root: event-loop lag: 177 ms (loop was busy, not waiting)
WARNING:root: event-loop lag: 283 ms (loop was busy, not waiting)
...

但为什么事件循环无法获取一个工作协程来处理 LLM 响应?在 Python 中,由于 GIL,同一时刻只有一个线程能执行代码。这意味着事件循环一定在别处执行一些 CPU 密集型工作。

但到底是什么?要弄清楚,应该使用一个分析器。

立竿见影的优化

在查看分析器之后,有几件事情很突出。首先,大量时间浪费在序列化和反序列化请求体上。默认情况下,aiohttp 使用内置的 json 库,但使用 orjson 可以显著加速。

(从 orjson 文档中截取的基准测试对比图,展示反序列化和序列化性能)

第二,并非所有 I/O 请求都能真正同时运行。事实证明,aiohttp 会话限制了可打开的 HTTP 连接数量,默认限制是 100。

因此,团队尝试增加并优化这个数字,但不是无限增加。在某个点上,继续增大这个值反而会损害性能。

如果重新运行测试,结果会好很多。然而,即使现在,随着智能体数量增加,延迟仍然在增加。

(一张图:不同 aiohttp 连接限制下的响应时间,作者自绘)

真正的瓶颈

有些人可能会责怪 aiohttp,说它不够快,或者因为它限制了连接数,所以无法实现真正的 I/O 并行。

对于这种想法,我们要创建一个更简单的代码示例。不再依赖那么多库和真实的 HTTP 调用,而是使用 async sleep 模拟 I/O 任务,用 sleep 模拟 CPU 密集型任务。

import asyncio
import random
import time

NUM_AGENTS = 50
CALLS_PER_AGENT = 30
IO_RANGE = (0.45, 0.55)           # simulated LLM latency: I/O wait
CPU_RANGE = (0.001, 0.002)        # simulated (de)serialization: CPU work

async def call_sub_agent() -> None:
    await asyncio.sleep(random.uniform(*IO_RANGE))
    time.sleep(random.uniform(*CPU_RANGE))

async def run_agent() -> None:
    await asyncio.gather(*(call_sub_agent() for _ in range(CALLS_PER_AGENT)))

async def handle_request(num_agents: int) -> None:
    await asyncio.gather(*(run_agent() for _ in range(num_agents)))

结果与之前看到的非常相似。当然,我们对 sleep 时间做了一些调整以生成漂亮的图表,但即使使用不同的数值,重要的点仍然不变:随着智能体数量增长,延迟增加。无法实现真正的并行。

当然,可以通过优化 CPU 密集型任务来降低曲线,但只能到一定程度。

(一张图:HTTP 实现与简化代码的对比,作者自绘)

此时,人们很容易责怪 Python 的异步运行时。是不是事件循环根本无法处理这么多并发任务?

事件循环本身极其高效。 如果对一个调度了数十万个仅执行 asyncio.sleep() 的协程的应用进行基准测试,它会扩展得非常好。只要任务确实是 I/O 密集型,asyncio 就能轻松管理海量并发。

(一张图:纯 asyncio 事件循环开销随任务数量增加的变化,作者自绘)

那么,为什么代码的表现如此不同?

因为这些任务并非纯粹的 I/O 密集型。

每当一个 LLM 响应到达时,事件循环都必须执行少量的 CPU 工作,然后才能继续处理下一个协程。单独来看,这些操作几乎不花时间。但累计起来,在数百或数千个并发调用中,它们就成了瓶颈

现在有些人可能会大喊:“这就是你在生产环境中使用 Python 的后果。它当然无法扩展。”

但这忽略了关键点。当然,GIL 让问题更严重,但即使使用 Java 或 Go 等其他语言,你仍然能打开的用于并行化 CPU 密集型任务的进程数量是有限的,因为你的机器有有限的核数。

从全局视角看

要找到正确的解决方案,有必要暂时退一步,不再关注哪些任务消耗了最多的 CPU 时间以及如何优化它们。相反,要认识到系统设计中存在一个根本性问题

重要的领悟是:CPU 工作的扩展方式与 I/O 不同。 每次 I/O 操作完成后,最终都需要一些 CPU 工作才能让应用程序继续。反序列化响应、计数令牌、验证数据或执行自定义逻辑,这些操作单独来看都不昂贵。但是,当数百或数千个智能体调用几乎同时完成时,这些微小的 CPU 任务会竞争同一 CPU 资源,最终成为瓶颈。

一旦理解了这一点,解决方案就变得清晰多了。不再试图将更多工作塞进单一事件循环,而是改变架构,使得没有一个单独的进程负责所有智能体

具体如何实现取决于架构。可以使用工作队列、多个服务、独立的作业或其他扇出机制。实现细节各不相同,但基本原则始终一样:分散 CPU 工作,而不是让一个进程包揽所有。

这并没有消除 CPU 工作。每个响应仍然需要反序列化、验证和处理。但是,这些工作不再累积在一个进程中,而是分布在多个进程、CPU 核心和机器上。这个区别很重要。瓶颈没有被消除,而是被分割成可以水平扩展的更小单元。

在 Planck 的案例中,他们引入了一个路由器,将每个请求扇出到多个工作进程。每个工作进程只负责一部分智能体,并拥有自己的事件循环和连接池。

分散工作负载同时解决了多个问题。 它阻止了延迟随着智能体增加而持续上升。它允许团队继续构建新的智能体,而不必担心拖慢现有智能体。它让工程师专注于解决业务问题,而不是花时间微优化每个 CPU 密集型操作。

最终思考

你很容易认为异步代码是无限可扩展的。毕竟,增加一个协程感觉几乎不花成本。但是异步 I/O 只消除了等待,它并没有消除计算。 最终,每个响应仍然需要执行 CPU 任务,而这就是可扩展性开始崩塌的地方。

这实际上不是 AI 智能体的问题,而是一个扇出问题。无论是单个请求触发数百个智能体,还是批量评估触发数千个 LLM 调用,其根本问题是一样的:每个响应之后所需的 CPU 工作最终会成为瓶颈。将工作负载分散到多个进程或作业中,通常比试图将所有工作推入单一事件循环更具可扩展性。

更广泛的教训远远超出了 Python 或 AI 智能体的范畴。好的工程不仅仅是编写高效的代码,更是设计能够随着增长而持续扩展的系统。 写得好的代码无法弥补设计糟糕的系统。你可以运行分析器,优化每一行代码,但它的影响是有限的。

有时,你必须退后一步,问自己:这种方法真的能扩展吗?还是我们应该考虑重新设计我们的系统?

来源:整理自互联网
免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多