1. 从一场“算力错配”说起Agent 为什么让 CPU 重新变成主角过去几年只要聊到 AI 基础设施话题几乎自动滑向 GPU显存多大、卡多贵、集群怎么堆、推理吞吐怎么压榨。CPU 在这场盛宴里长期扮演“配角”——负责喂数据、做预处理、管调度真正被 spotlight 照到的时刻少得可怜。但如果你最近在跑 Agent 类应用可能会发现一个反直觉的现象GPU 利用率上不去CPU 反而先成了瓶颈。这不是错觉而是 Agent 这种负载形态对计算资源提出的全新要求。Agent 和传统的大模型推理、训练任务有本质区别。传统推理是“一问一答”输入一段 prompt模型吐出一段 completion整个流程是线性的、可预测的。而 Agent 是“感知—规划—行动—观察”的循环体它要调用工具、读写文件、解析返回结果、维护记忆、做条件分支、处理异常重试。这些环节里真正跑在 GPU 上的只有模型前向计算那一小段其余大量工作——JSON 解析、字符串匹配、状态机跳转、I/O 调度、并发控制——全部落在 CPU 上。换句话说Agent 把原本集中在 GPU 上的计算压力重新分散回了 CPU 侧。这篇文章想聊的就是这件事在 GPU 依然是绝对算力核心的前提下CPU 是怎么因为 Agent 的兴起而“重新上桌”的。我会从负载特征、架构选型、实操调优、常见坑几个角度展开把我在实际项目里踩过的经验和观察到的规律整理出来。适合正在做 Agent 开发、推理服务部署、或者单纯对“为什么我的 GPU 闲着但任务就是慢”感到困惑的读者。不管你是刚接触 Agent 的新手还是已经在调优推理链路的工程师应该都能从下面这些内容里找到能直接用的东西。2. Agent 负载到底特殊在哪拆开看 CPU 和 GPU 的分工2.1 传统推理 vs Agent 推理计算密度的巨大差异先建立一个直观对比。传统 LLM 推理假设你部署了一个 7B 模型做文本生成一次请求的流程大致是tokenize 输入 → GPU 前向计算 → detokenize 输出。GPU 在这段时间里几乎满负荷运转CPU 只负责轻量的前后处理。这种情况下GPU 是绝对瓶颈CPU 占用率通常很低甚至可以用“闲置”来形容。Agent 推理完全是另一幅画面。一个典型的 Agent 任务比如“帮我查一下最近三天的订单异常并生成报告”执行链路可能是这样的接收用户指令调用 LLM 做任务规划GPU 计算短解析规划结果提取需要调用的工具和参数CPU 计算JSON 解析调用订单查询 API等待网络返回CPU 调度 I/O 等待对返回的原始数据做清洗、过滤、聚合CPU 计算可能很重把中间结果拼成新的 prompt再次调用 LLM 做分析GPU 计算短解析分析结果判断是否需要继续调用工具CPU 计算 条件分支循环 3-6 直到任务完成生成最终报告格式化输出CPU 计算你会发现GPU 只在第 1、5 步真正工作而且每次计算量都不大——因为 Agent 的 prompt 通常很长但生成长度很短属于典型的prefill 重、decode 轻的负载。而 CPU 在第 2、3、4、6、8 步持续忙碌尤其是数据清洗和 JSON 解析这类操作在工具返回数据量大、嵌套层级深的时候CPU 开销会急剧上升。一个实测数据在某次订单分析 Agent 的压测中单次任务端到端耗时 8.2 秒其中 GPU 计算累计仅 1.7 秒CPU 处理累计 4.9 秒剩余 1.6 秒是网络等待。GPU 利用率峰值只有 23%而 CPU 单核直接跑满。2.2 为什么 CPU 成了新瓶颈三个核心原因第一Agent 的控制流极其复杂。传统推理是数据流驱动一次前向传播结束就结束。Agent 是控制流驱动里面有大量的 if-else、循环、异常捕获、重试逻辑。这些逻辑本身不复杂但执行频率极高而且往往涉及字符串操作和状态判断天然适合 CPU 而不适合 GPU。GPU 擅长的是大规模并行数值计算你让它去做条件分支等于让卡车去跑拉力赛不是不能跑是效率极低。第二工具调用的序列化开销被低估了。Agent 调用工具时需要把参数序列化成 JSON 或类似格式工具返回后又要反序列化。Python 的json.loads和json.dumps在处理大对象时性能并不理想尤其是嵌套字典和列表混合的结构。如果工具返回的是 CSV 或 XML解析开销更大。这些操作全部是 CPU 密集型而且很难通过异步来掩盖因为后续的 LLM 调用依赖解析结果。第三记忆管理和上下文拼接是隐藏的 CPU 杀手。Agent 通常需要维护短期记忆和长期记忆每次调用 LLM 前要把相关记忆片段检索出来、拼接进 prompt。向量检索本身可能走 GPU 或专用加速器但检索结果的排序、去重、截断、格式化全是 CPU 活。当记忆库大了之后这部分开销会线性增长。2.3 GPU 在 Agent 场景下的真实利用率别被峰值骗了很多团队看监控面板时看到 GPU 利用率偶尔冲到 80% 就觉得资源用得很满。但在 Agent 场景下要看的是时间加权平均利用率而不是峰值。因为 Agent 的 GPU 计算是脉冲式的来一个请求GPU 忙几百毫秒然后闲几秒等 CPU 处理完再进入下一轮。如果并发度不够GPU 大部分时间在等 CPU 喂数据。这就引出一个关键结论Agent 场景下GPU 和 CPU 的配比不能照搬训练集群的经验。训练时 GPU:CPU 可能是 8:1 甚至更高因为 CPU 只负责数据加载。但 Agent 推理服务里GPU:CPU 可能要到 1:2 甚至 1:4才能让 GPU 不饿着。具体比例取决于 Agent 的工具调用密度和数据处理复杂度后面我会给一个估算方法。3. 让 CPU 高效“上桌”架构选型和实操调优3.1 CPU 选型核心数、主频、缓存怎么权衡Agent 负载对 CPU 的需求和传统 Web 服务不太一样。Web 服务通常是 I/O 密集型核心多一点、主频低一点问题不大。Agent 负载是CPU 密集 I/O 混合而且 CPU 密集部分往往是单线程的比如一个 JSON 解析任务很难拆到多核所以主频和单核性能比核心数更重要。我的经验是选 Agent 服务器的 CPU 时优先看这三个指标单核睿频越高越好直接决定 JSON 解析、字符串处理的速度。建议不低于 4.0GHz。L3 缓存Agent 的数据处理往往有局部性大缓存能显著减少内存访问延迟。32MB 起步最好 64MB 以上。内存带宽当并发 Agent 数量多时内存带宽会成为瓶颈。DDR5 比 DDR4 有明显优势。核心数方面建议至少 16 核起步因为 Agent 服务通常要同时处理多个会话每个会话可能占用 1-2 个核做数据处理。如果并发会话数超过 5032 核甚至 64 核会更稳妥。但注意不要盲目堆核心因为如果单核性能不够每个会话的处理时间会拉长反而降低整体吞吐。一个实际对比在 50 并发 Agent 会话的压测中32 核 3.5GHz 的 CPU 和 16 核 4.5GHz 的 CPU后者端到端延迟反而低 18%。原因是单会话的处理链路是串行的单核快比核多更管用。3.2 把 JSON 解析从热路径上挪走几个立竿见影的优化JSON 解析是 Agent 里最容易被忽视的性能黑洞。Python 标准库的json模块是纯 C 实现的已经不算慢但在高频调用下依然可观。我试过几个优化手段效果从高到低排列第一用orjson替换标准库。orjson是 Rust 实现的 JSON 库序列化速度大约是标准库的 3-5 倍反序列化大约 2-3 倍。安装很简单pip install orjson使用上几乎无缝替换import orjson # 替代 json.loads data orjson.loads(json_bytes) # 替代 json.dumps注意 orjson.dumps 返回 bytes output orjson.dumps(data).decode(utf-8)唯一需要注意的是orjson对某些边缘格式的支持和标准库略有差异比如它默认不支持NaN和Infinity需要显式开启OPT_NON_STR_KEYS等选项。但在 Agent 场景下工具返回的数据通常是规范的问题不大。第二避免重复解析。很多 Agent 框架会把工具返回的 JSON 解析成 dict然后在多个环节反复遍历、修改、再序列化。我的做法是在工具返回后只解析一次后续所有操作都在 dict 上做直到最终需要传给 LLM 时才序列化。中间环节如果需要传递直接传 dict 引用不要反复 dumps/loads。第三对大 JSON 做流式解析。如果工具返回的数据量很大比如几 MB 的日志一次性解析会占用大量内存和 CPU 时间。可以用ijson做流式解析只提取需要的字段import ijson with open(large_data.json, rb) as f: for record in ijson.items(f, results.item): # 只处理需要的字段 process(record[id], record[status])这样内存占用从 O(n) 降到 O(1)CPU 时间也大幅减少因为跳过了不需要的字段。3.3 并发模型选择异步不是银弹线程池也有春天Agent 服务的并发模型直接决定 CPU 利用率。Python 生态里常见的选择有 asyncio、多线程、多进程以及混合模式。我的建议是根据 Agent 的阻塞特性来选如果 Agent 主要是等 LLM API 返回网络 I/O用 asyncio 最合适单线程就能扛很高并发。如果 Agent 有大量本地 CPU 计算JSON 解析、数据清洗asyncio 会被阻塞因为 Python 的 GIL 让 CPU 密集任务无法真正并行。这时候要用run_in_executor把 CPU 任务丢到线程池或进程池。如果 CPU 计算特别重比如要做本地向量检索或复杂规则引擎直接用多进程每个进程跑一个 Agent 会话避免 GIL 限制。我常用的混合模式是这样的import asyncio from concurrent.futures import ProcessPoolExecutor async def handle_agent_session(session_id, input_data): # 网络 I/O 用 async llm_response await call_llm_async(input_data) # CPU 密集任务丢到进程池 loop asyncio.get_running_loop() with ProcessPoolExecutor() as pool: parsed await loop.run_in_executor( pool, heavy_parse_function, llm_response ) return parsed这样既保留了 asyncio 的高并发 I/O 能力又避免了 CPU 任务阻塞事件循环。实测下来在 100 并发、每会话有 3-4 次 CPU 密集解析的场景下这种混合模式比纯 asyncio 吞吐高 2.3 倍。注意进程池的进程数不要超过 CPU 物理核心数否则上下文切换开销会吃掉收益。一般设为os.cpu_count()即可如果 CPU 计算特别重可以设为os.cpu_count() - 1留一个核给主进程做调度。3.4 内存管理别让 GC 在关键时刻拖后腿Python 的垃圾回收在 Agent 场景下可能成为隐藏的延迟尖刺。Agent 会频繁创建和销毁大量临时对象——解析出的 dict、拼接的字符串、中间结果列表。当这些对象积累到一定数量GC 会触发暂停所有线程。如果 GC 发生在 LLM 调用返回后的关键路径上用户会感知到明显的卡顿。我的做法是调整 GC 阈值并手动管理大对象import gc # 提高阈值减少 GC 频率 gc.set_threshold(100000, 50, 50) # 在 Agent 会话结束时手动触发 def cleanup_session(session): session.memory.clear() session.context None gc.collect()另外对于频繁创建的小对象可以用对象池或__slots__来减少内存分配开销。比如 Agent 的中间状态对象定义__slots__后内存占用能降 30% 以上创建速度也更快。4. 一个可复现的 Agent 服务调优实录4.1 场景设定与初始状态为了把上面的理论落地我搭了一个最小化的 Agent 服务来演示调优过程。场景是“订单异常分析 Agent”功能是接收用户查询调用订单 API 获取数据做异常检测生成报告。技术栈是 FastAPI asyncio 本地 LLM 推理走 GPU。初始版本的问题很典型50 并发下P99 延迟 12 秒GPU 利用率峰值 35%CPU 单核跑满整体吞吐只有 8 QPS。监控面板上 GPU 大部分时间在 20% 以下晃悠CPU 却持续 90% 以上。4.2 第一步定位 CPU 热点先用py-spy做火焰图分析pip install py-spy py-spy record -o profile.svg --pid agent_pid火焰图显示CPU 时间分布大致是JSON 解析 38%字符串拼接 22%数据过滤 18%其他 22%。JSON 解析是最大头主要来自订单 API 返回的嵌套数据。4.3 第二步替换 JSON 库并优化解析逻辑把标准库换成orjson同时把“解析—过滤—再序列化”的链路改成“解析一次—过滤—直接传 dict”import orjson def process_order_response(raw_bytes): # 一次性解析 data orjson.loads(raw_bytes) # 在 dict 上直接过滤不重新序列化 anomalies [ order for order in data[orders] if order[status] abnormal ] # 只在最终传给 LLM 时序列化 return orjson.dumps({anomalies: anomalies}).decode(utf-8)这一步之后JSON 相关 CPU 时间下降约 60%P99 延迟从 12 秒降到 8.5 秒。4.4 第三步引入进程池处理 CPU 密集任务把数据过滤和报告生成逻辑丢到进程池from concurrent.futures import ProcessPoolExecutor import asyncio executor ProcessPoolExecutor(max_workers8) async def analyze_orders(raw_data): loop asyncio.get_running_loop() result await loop.run_in_executor( executor, _analyze_sync, raw_data ) return result def _analyze_sync(raw_data): # 这里做所有 CPU 密集操作 data orjson.loads(raw_data) anomalies filter_anomalies(data) report generate_report(anomalies) return report这一步让 CPU 密集任务真正并行起来主事件循环不再被阻塞。P99 延迟降到 5.2 秒吞吐提升到 19 QPS。4.5 第四步调整 GC 和内存分配加上 GC 阈值调整和__slots__优化后P99 进一步降到 4.1 秒吞吐 24 QPS。GPU 利用率峰值提升到 68%平均利用率从 18% 升到 45%。最终对比指标初始版本优化后提升幅度P99 延迟12.0s4.1s66%吞吐8 QPS24 QPS200%GPU 平均利用率18%45%150%CPU 单核峰值100%72%28%这个案例里最关键的认知转变是不要只盯着 GPU 优化先把 CPU 侧的浪费找出来。很多时候 GPU 利用率低不是因为 GPU 不够快而是因为 CPU 喂数据太慢。5. 常见问题与排查技巧实录5.1 Agent 服务 CPU 占用高但吞吐上不去怎么查这是最常见的问题。排查顺序建议如下先看是不是单核跑满。用htop看每个核的利用率如果只有一个核 100%其他核闲着说明有串行瓶颈。常见原因是 asyncio 事件循环里跑了同步 CPU 任务。再看 GC 频率。在代码里加gc.callbacks统计 GC 次数和耗时。如果 GC 耗时占比超过 5%就需要调整阈值或手动管理。然后看 JSON 解析占比。用py-spy火焰图确认。如果超过 30%换orjson或做流式解析。最后看锁竞争。如果用了多线程检查有没有全局锁或频繁的锁获取释放。Python 的 GIL 本身就是一把大锁多线程做 CPU 任务基本无效。5.2 GPU 利用率忽高忽低像心电图一样这是 Agent 负载的典型特征但如果波动太大说明 CPU 和 GPU 的流水线没对齐。解决办法有两个增加并发度。让多个 Agent 会话同时跑当一个会话在 CPU 处理时另一个会话可以占用 GPU。一般并发度设为 GPU 计算时间的倒数乘以 CPU 处理时间。比如 GPU 算 0.5 秒CPU 处理 2 秒那至少需要 4 个并发才能让 GPU 不闲。做请求批处理。如果多个 Agent 会话在同一时间段需要调用 LLM可以把它们的 prompt 拼成一个 batch 一起送 GPU提高 GPU 利用率。但要注意 batch 太大会增加延迟需要权衡。5.3 工具调用返回大数据时服务卡死这通常是内存和 CPU 双重压力导致的。工具返回几百 MB 数据时一次性加载到内存再解析会触发大量内存分配和 GC。解决方案用流式解析边读边处理不把整个数据加载到内存。设置工具返回的数据量上限超过就截断或分页。把大数据处理放到独立进程避免影响主服务。5.4 常见问题速查表现象可能原因排查手段解决方向CPU 单核 100%同步 CPU 任务阻塞事件循环py-spy 火焰图丢到进程池GC 频繁触发临时对象过多gc.callbacks 统计调阈值 对象池GPU 利用率低CPU 喂数据慢对比 CPU/GPU 耗时优化解析 增并发延迟尖刺GC 或锁竞争分段计时手动 GC 无锁设计内存持续增长对象未释放objgraph 分析显式清理 弱引用5.5 几个我踩过的坑坑一以为 asyncio 能解决一切。刚开始做 Agent 时我把所有逻辑都写成 async结果 CPU 密集任务把事件循环堵死所有请求排队。后来才明白asyncio 只适合 I/O 密集CPU 密集必须用进程池。坑二过度优化 JSON 解析。有一段时间我沉迷于换各种 JSON 库从ujson换到orjson再换到msgspec收益从 3 倍降到 1.2 倍。后来发现真正的瓶颈是重复解析和序列化而不是库本身。先优化逻辑再优化工具。坑三忽略工具调用的网络延迟。Agent 调用外部 API 时网络延迟可能比 CPU 处理时间还长。如果串行调用多个工具总延迟会累加。解决办法是并行调用无依赖的工具用asyncio.gather一次性发出去。坑四进程池设太大。一开始我把进程池设为 32结果上下文切换开销巨大吞吐反而下降。后来改成 8等于物理核数性能才稳定。进程数不是越多越好匹配物理核数才是最优。6. 一些关于 CPU 和 GPU 配比的个人经验最后聊一个实际部署时经常被问到的问题Agent 服务到底该配多少 CPU 和 GPU。我的经验公式是CPU 核心数 ≈ 并发会话数 × 每会话 CPU 密集任务占比 × 1.5GPU 数量 ≈ 并发会话数 × 每会话 GPU 计算时间 / GPU 单次计算时间举个例子假设 100 并发会话每会话端到端 5 秒其中 CPU 处理 3 秒、GPU 计算 1 秒、网络等待 1 秒。那么 CPU 需要大约 100 × 0.6 × 1.5 90 核GPU 需要 100 × 1 / 5 20 路并发计算能力。如果单张 GPU 能同时跑 10 路就需要 2 张 GPU。这个公式是粗略估算实际还要考虑峰值波动和冗余。但核心逻辑是Agent 场景下 CPU 需求被严重低估了配比时不要按训练集群的经验来。另外如果预算有限优先升级 CPU 而不是 GPU。因为 Agent 的瓶颈往往在 CPU 侧GPU 再快CPU 喂不上数据也是白搭。我见过太多团队花大价钱买 GPU结果 CPU 是几年前的旧型号整体性能被拖累。一个反直觉的观察在某些 Agent 场景下把 CPU 从 16 核升级到 32 核带来的吞吐提升比把 GPU 从 A100 换成 H100 还大。因为瓶颈根本不在 GPU。这个内容后续还可以往几个方向扩展一是 Agent 框架层面的优化比如 LangChain、AutoGen 这些框架本身的 CPU 开销分析二是分布式 Agent 的调度策略怎么把 CPU 密集和 GPU 密集的任务分开部署三是用 Rust 或 Go 重写 Agent 的 CPU 密集模块彻底绕开 Python 的 GIL 限制。这些我后续会陆续整理有兴趣的可以持续关注。