本地大语言模型代理实用化的基础设施

TL;DR · AI 摘要
本地部署LLM代理需解决推理速度与长会话状态管理问题,通过优化vLLM服务器和结构化世界状态,可将单次调用耗时从15秒降至2秒以内,支持科学工作流的可复现性需求。
核心要点
- 使用vLLM优化推理性能,单次调用耗时从15秒降至2秒内
- 构建结构化世界状态避免上下文溢出,支持80+工具调用长会话
- 采用Qwen3.6-27B/Gemma4-31B等开源模型实现科研工作流可追溯性
结构提纲
按章节快速跳转。
本地部署LLM代理需超越聊天机器人功能,满足科学工作流的可复现性和状态持久化需求。
每次调用携带36k token固定前缀与不断增长的历史记录,导致单次响应10-15秒并易崩溃。
通过编译级缓存、KV缓存压缩与批处理优化,显著降低多轮调用延迟,实测A100/H100性能提升80%以上。
设计独立状态存储与动态修剪机制,使80+步骤分析任务在内存中稳定运行不中断。
本地LLM代理需系统性基础设施支撑,开源模型+工程优化可媲美云端服务,尤其适合科研与合规场景。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 本地LLM代理实用化基础设施
- 性能瓶颈分析
- 固定前缀(36k tokens)重复加载
- 对话历史指数级增长
- 加速方案
- vLLM推理引擎优化
- KV缓存压缩与批处理
- 状态管理方案
- 结构化世界状态设计
- 动态上下文修剪机制
- 适用场景
- 科研工作流(如单细胞分析)
- 合规/隐私敏感领域
金句 / Highlights
值得收藏与分享的关键句。
单次工具调用初始耗时15秒,经vLLM优化后降至2秒内,关键在于减少重复加载系统提示与工具Schema。
对话历史每轮增长,第40轮已达数万token,导致上下文溢出崩溃,必须引入结构化世界状态替代纯对话记忆。
使用Qwen3.6-27B或Gemma4-31B等开源模型,在工具驱动型工作负载中表现接近闭源模型,且支持完全自控。
让本地LLM代理真正有用的基础设施
来源URL:https://towardsdatascience.com/the-infrastructure-behind-making-local-llm-agents-actually-useful/
发布时间:2026-05-28T15:00:00+00:00
本地部署听起来很简单:下载权重、启动服务器、发送请求。这对聊天机器人有效,但并不能自动适用于智能体场景。我目前正在开发一个自动化单细胞RNA测序分析的智能体。其核心理念是:给定原始数据后,该智能体能自主运行完整流程,包括决定调用哪些工具、解读结果,并逐步推进分析。
你或许会问,为何不直接使用Claude Code搭配单细胞分析技能(Skill)?简短的回答是:科研工作流需要更多支持。技能本质上只是提示词,因此可能被覆盖或忽略。更重要的是,科研需要可重复性和溯源追踪——确切记录使用了哪些参数、过滤了哪些细胞、哪种聚类分辨率生成了什么结果等等。这类记录必须结构化且持久化,而非从对话中重构获得。对于长时间运行的会话,还需要显式的环境状态管理,而不能依赖上下文压缩来保留关键信息。这些功能必须刻意构建。在本地模型上构建所有这些功能意味着你完全掌控基础设施,这正是本文关注的重点。
我们构建的智能体运行在机构高性能计算(HPC)硬件上,采用近期开源权重模型。人们容易认为开源模型不够强大,但这一观点正逐渐改变。近期发布的Qwen3.6–27B和Gemma 4–31B等模型已能有效处理结构化、工具驱动的工作负载(若想持续跟踪开源进展,Interconnects AI提供了很多值得关注的内容)。这也是本地部署的重要原因。我们的智能体也支持Claude和GPT等云API,但使用这些服务时,本文即将描述的基础设施将完全不可见——这些问题早已由他人解决。当你自行托管模型时,这些挑战就将成为你的责任。
首次运行模型时,它仅实现了狭义上的"工作":模型调用工具、工具执行、分析推进。但它尚未达到真正的可用性。一个简单的单细胞分析可能包含50-80次工具循环调用。每次调用都携带相同的固定开销:系统提示词、工具模式定义以及不断增长的对话历史。仅系统提示词和工具模式定义就占用了约3.6万个token。在模型做出任何决策前,必须先读取数万token的指令和工具定义。然后在下一轮迭代再次读取,如此反复。每次迭代耗时10-15秒,长时间运行最终会因上下文溢出崩溃,并丢失所有内存中的分析状态。本文将解决这两个问题。
第一部分:加速推理过程
深入具体优化前,先理解智能体循环每次迭代的实际运行机制。下图展示了一个迭代过程:智能体将系统提示词、工具模式和完整对话历史发送至模型。模型读取全部内容后决定调用哪个工具。工具执行后返回结果,该结果会被追加到历史记录中,随后进入下一轮迭代。这里需注意两点:系统提示词加工具模式构成的固定前缀(约3.6万token)每轮都会重复发送;对话历史随每次迭代增长。到第40轮时,模型面对的已不再是简短指令,而是一份包含多次工具调用、输出及中间结果的冗长分析日志。这两点严重影响了智能体性能。

图1:智能体循环的一次迭代。固定前缀在每轮重复发送,对话历史随迭代增长。(作者绘制)
1.1 CUDA图:将每token数百条指令精简为一条
理解这一点需要了解GPU生成单个token时的内部机制。
解码阶段生成每个token需要按顺序执行注意力、全连接层、归一化等GPU核函数。每次核函数启动都会产生CPU侧的小型协调开销:CPU需向GPU排队发送指令,精确指定运行哪个核函数、张量形状及内存指针。对于我们使用的270亿参数模型,这意味着每生成一个token就需要数百次独立调度。单次调度虽小,累积起来却非常可观。
CUDA 图消除了这种开销。在处理任何实际请求之前,vLLM 可以运行一个预热阶段,将解码步骤中所有的内核调度记录到一个可重放的对象中。之后,生成每个标记只需向 GPU 发送一条指令,而非数百条。仅通过这一项改动,延迟就降低了约 20-25%,且无需修改模型本身。
这很棒,但 CUDA 图也要求静态张量形状,这意味着图会针对特定的批量大小和序列长度进行编译。对我们而言,这意味着首次启动时间比后续启动更长。后续启动速度更快。如图 8 所示,对于执行数百次迭代的代理程序,累积效果非常显著。
1.2 内存容量优化
神经网络中的每个权重都是一个数值,存储该数值的格式会影响内存占用量和 GPU 的运算速度。现代大语言模型(至少在训练时)的标准格式是 BF16,每个权重存储为 16 位浮点数。对于 Qwen3.6–27B 的 270 亿参数,仅加载模型就需要约 56GB 的权重数据。
FP8 将每个权重存储为 1 字节而非 2 字节。同一模型现在仅需约 31GB。我们可以将释放的内存用于 KV 缓存,这是存储对话上下文的部分。更大的 KV 缓存意味着模型可以在内存耗尽前处理更长的输入。但不同模型对释放的内存利用率不同。实际获得的上下文容量取决于模型架构本身。一个关键指标是每标记 KV 内存,即模型存储单个上下文标记所需的 GPU 内存量。
这就是为什么参数量相近的两个模型在实际表现上会有差异。例如,Gemma 4–31B 每标记需要约 1.1MB 的 KV 缓存。而 Qwen3.6–27B 根据注意力层的统计方式,在保守估计下接近 256KB/标记。这意味着同样的剩余 GPU 内存可以让 Qwen 处理比 Gemma 多得多的标记。
例如,假设在两块 80GB 的 GPU 上加载模型并预留一些运行时开销后,我们有约 82GB 可用于 KV 缓存。使用 Gemma 时,82GB / 1.1MB ≈ 74,000 标记。而 Qwen 若采用 256KB/标记,则 82GB / 256KB ≈ 320,000 标记。实际上,模型配置的最大上下文长度会限制这一数值(若使用 YaRN,可扩展至 100 万标记),但重点在于:Qwen 在长上下文任务中能更高效地利用相同的 GPU 内存。
回到模型权重的 FP8 表示,硬件张量核心执行 FP8 数字乘法需要专用的 FP8 算术单元,这是 NVIDIA 在 Hopper 架构中引入的。H100 支持此功能,而 A100 不支持。因此,我们在 A100 上使用 BF16 权重,在 H100 上使用 FP8 权重。速度提升直接源于此。在解码过程中,GPU 需要在生成每个标记时从内存读取模型权重。当批量大小为 1(单用户代理会话的情况)时,内存读取成为瓶颈,而非计算本身。较小的权重意味着每次读取的数据量减少,从而加快生成速度。
除了模型权重外,FP8 还有一个优势适用于所有 GPU:将 KV 缓存向量存储为 FP8 而非 BF16,使每标记成本减半(Qwen3.6–27B 从 256KB 降至 128KB),直接使内存中可容纳的标记数量翻倍。向量以 FP8 存储以节省内存,但在实际参与注意力计算时会反量化回 BF16,因此无需 FP8 张量核心。

图2:使用 BF16(A100)和 FP8(H100)时 Qwen3.6–27B 的权重与 KV 缓存对比。(作者绘制)
还有一个因素进一步提升了内存效率:跨多 GPU 的张量并行会将权重矩阵拆分到各卡上。每块 GPU 现在只保存一半的权重,从而释放更多空间用于 KV 缓存。在 A100 上,每块 GPU 的权重占用从 56GB 降至 28GB,使可用 KV 缓存从 16GB 增加到 44GB。这使得 A100 硬件能够处理约 180,000 标记的上下文窗口,足以满足完整分析会话的需求(张量并行不同于 FSDP,后者是另一种分布式工作负载方法。您可通过我的另一篇文章此处了解更多)。

图3:张量并行释放更多 KV 缓存空间。(作者绘制)
1.3 前缀缓存
回想我们之前提到的固定前缀:系统提示和工具模式,它们会在每次代理循环迭代中发送。对于该代理,这部分约为 36,000 标记。在每次迭代中,模型必须从头开始读取和处理所有这些标记,才能做出决策。这意味着即使前缀内容从未改变,每次调用仍需对 36,000 标记执行完整的注意力计算。
前缀缓存通过存储模型已处理过的任何标记序列的键和值向量来解决这一问题。如果下一个请求以相同的前缀开头,则这些向量将直接从缓存中检索,而不是重新计算。模型仅在第一个请求时承担完整的预填充成本。同一会话中的后续请求可以直接跳转到尾部的新标记。但如果会话中途前缀发生更改(例如编辑系统提示或添加MCP工具列表),则需要从头开始重新读取整个历史记录。同样,如果在会话中途更换模型,也会出现这种情况。你可能在Claude Code中见过类似情况,当尝试中途更换模型时,它会提示需要重新读取整个消息。
对于代理循环而言,这一点尤为重要,因为固定部分较大,而每次迭代新增的部分相对较小。随着会话进展,缓存命中率实际上会提高。到第40次迭代时,大部分请求都是缓存的历史数据,只有最新的添加部分需要重新计算。

图4. 跨迭代的前缀缓存。固定前缀仅计算一次并缓存。在会话后期,大部分请求都是缓存命中。(作者供图)
为了衡量实际影响,我们通过vLLM服务器运行了代理的真实系统提示和工具架构(36K标记),分别在启用和禁用前缀缓存的情况下,在A100和H100硬件上进行了测试。我们测量了冷启动时的首次生成令牌时间(TTFT),此时缓存为空且必须从头计算完整前缀,以及缓存已填充的热启动请求的时间。在A100上,冷启动TTFT为11,470毫秒,启用热缓存后降至706毫秒。在H100上,冷启动时间为2,655毫秒,热启动时降至249毫秒。这是因为无需重新计算前缀,仅处理尾部的新标记。

图5: 前缀缓存在热启动时具有显著影响,尤其是在会话后期,因为它不会重新计算旧标记。(作者供图)
1.4 推测解码
解码本质上是顺序进行的。模型生成一个标记,将其附加到上下文中,然后以自回归方式生成下一个标记。每个标记都依赖于之前的全部标记,因此无法像批处理那样并行化。对于单用户代理会话(批量大小为1),这种顺序瓶颈是吞吐量的主要限制因素。
推测解码通过引入一个小规模草稿模型来绕过这一限制。草稿模型会快速且廉价地提出接下来的k个标记。主模型随后通过一次并行前向传递验证所有这k个提议的标记。由于主模型同时读取k个标记而非逐个生成,验证步骤的成本大致相当于正常生成单个标记的成本。如果大多数提议被接受,即可以接近单个标记的成本获得k个标记。

图6: 推测解码可以显著加速标记生成,但前提是大多数生成的标记最终被接受。(作者供图)
关键变量是接受率。如果草稿模型的提议持续错误,主模型会拒绝它们并退回到逐个生成的方式,但仍然需要承担运行草稿路径的开销。具体平衡点取决于草稿模型、提议标记数量、硬件和实现方式。在我们的设置中,接受率低于约40%时并不划算。
此时草稿模型的选择变得至关重要。我们最初尝试了DFlash(一种独立的小型模型作为草稿)。在我们的工作负载下,接受率为4%至7%,远低于平衡点,实际上导致速度变慢。(公平地说,截至本文撰写之日,Z实验室的DFlash创建者表示Qwen3.6–27B的草稿模型仍在训练中,完成后可能会有所改善。)但在我们的案例中,Qwen3.6–27B内置了一个更好的解决方案:多标记预测头(MTP),这是一个与主模型共同训练的辅助预测头,直接嵌入权重中。由于MTP头与主模型共同训练,并使用模型自身的隐藏状态,其提议与模型原本会生成的内容高度一致。
我们在A100和H100硬件上针对真实代理会话测量了MTP的接受率。中位数约为89%的接受率表明MTP处于有利地位,远高于平衡点,并且在分析工作流程的不同阶段表现稳定。

图7: Qwen3.6–27B的MTP接受率显著加速了解码过程。(作者供图)
#### 综合运用
每个优化方案都是累积式基准测试的结果,每个配置都建立在之前的基础上。我们使用真实36K token系统提示的Qwen模型,在A100和H100硬件上对完整堆栈进行了测量,完全匹配了代理会话的实际条件。

图8:每个优化方案都叠加在前一个基础上。CUDA图主导了解码吞吐量,而前缀缓存则主导了预热阶段的首次生成时间。(作者供图)
几个关键点值得注意。CUDA图是解码性能的主要提升因素,在A100上带来约3倍提升,在H100上达到6倍提升。值得注意的是,H100的基础性能实际上比A100慢,这看似反直觉(需要注意的是,GPU之间的通信协议至关重要,除了GPU类型本身。最佳实践是通过NVSwitch使用NVLink,其次是NVLink Bridge,最后才是PCIe)。这反映了CPU调度开销在编译图形前对FP8内核的严重限制。一旦启用图形编译,H100便持续领先。
FP8键值缓存和前缀缓存对解码吞吐量的影响不大,这是预期结果。它们分别解决内存容量和预填充延迟问题,而非生成速度。前缀缓存的效果在首次生成时间瀑布图中清晰可见:前三次配置呈水平线,启用缓存后陡然下降。
MTP是两个GPU上第二大的解码贡献者,在A100上提升约37%,在H100上提升约20%。
第二部分:维持长会话存活
使用云端模型时,上下文管理容易被忽视。通常情况下,上下文窗口足够应对普通聊天会话,且服务基础设施由平台托管。但在本地运行模型时,上下文窗口成为硬件预算的关键指标。更大的上下文意味着更大的键值缓存,进而占用更多GPU内存。在我们早期的A100配置中,有效上下文窗口约为74K token。
单个单元格分析可能需要50到80+次迭代。每次迭代都会将工具调用、工具结果、中间观察、图表、错误、修正和用户约束追加到对话历史中。若不进行管理,当上下文填满时,API会返回超出长度错误,导致会话终止,同时丢失所有内存中的分析状态,包括存储处理后数据集的AnnData对象。
因此,问题不仅在于让模型运行更快。代理还需要足够持久以完成整个流程。
上下文管理听起来简单:跟踪窗口使用情况并在接近上限时修剪。但在实践中,天真的实现方式可能会出错。
Anthropic提供了代理上下文工程指南,并描述了三种适用于长期任务的策略:压缩、结构化笔记记录和多代理架构。压缩是最常见的解决方案,对于通用助手效果良好。当上下文填满时,对话历史会被送回模型进行摘要,会话继续使用压缩版本。
对于通用助手而言,这种方法可行。但对于科学分析来说,它会丢失关键信息。
科学工作流的问题在于,散文式摘要会恰好丢失所需信息。“分析对数据进行了聚类和质量控制”是一个有效的总结,但它丢弃了QC阈值、聚类分辨率、保留细胞数量等精确参数。这些参数正是代理重现步骤、描述方法或回答操作细节问题所必需的。它们不是装饰性细节,而是分析的核心。科学代理需要精确记录,而非仅保留概要。
除了压缩带来的概念性问题,还有更基础的上下文管理失败模式。首先是固定成本核算问题。每个API调用包含系统提示、工具架构和预留生成预算,甚至在第一条历史消息出现之前就已消耗资源。对于本代理而言,系统提示和工具架构本身就需要约36K token。如果修剪阈值未预先扣除这些固定成本,代理可能会在历史消息尚未添加时就超出预算。
第三个问题是上下文限制发现。上下文限制是所有预算计算的分母。如果模型元数据查询失败,代码静默回退到硬编码默认值,后续所有修剪决策都将错误。
更好的方法是停止将对话历史视为事件记录。对于科学工作流,已经存在更可靠的记录:代理执行的每一步骤的结构化日志,包含精确参数和结果。我们称之为世界状态。
世界状态是一个Python对象,用于跟踪分析过程的进展。每次工具调用完成后,都会向其中写入一个结构化的条目:执行了哪个步骤、使用了哪些参数以及结果如何。这些信息会在每次迭代时序列化到系统提示中。它占用不到1,000个标记,包含精确的参数而非文本摘要,并且存储在永不被截断的系统提示中。当旧的工具结果从消息历史中移除时,分析记录仍会完整保留。
这改变了我们对截断策略的看法。无需将消息历史视为必须保留的珍贵记录,因为真正的记录存在于其他地方。你可以大胆地截断消息历史,因为它只是有用的上下文。世界状态才是事实依据。

图9:由于科学记录保存在结构化世界状态中,对话历史可以被截断。(作者绘制)
世界状态解决了“保留什么”的问题。剩下的修复措施则解决了“何时以及如何截断”的问题。

图10:上下文窗口并非全部可用于历史记录。需先扣除固定提示、世界状态、完成预留和安全边际的成本。(作者绘制)
第一个修复是停止将整个上下文窗口视为可用的历史记录。可用预算通过预先扣除固定成本计算得出:
available = (
context_limit
- tool_schema_tokens
- system_tokens
- COMPLETION_RESERVE
- safety_margin
)对于262K的上下文窗口,在典型开销下,剩余约219K标记用于消息历史。而对于32K的紧凑上下文,则只能提供几千标记。这非常有用——它能立即告诉你长时间会话在这种上下文大小下无法运行,而不是在三次迭代后默默崩溃。
第二个修复是自校准标记计数。我们不再试图匹配Qwen的确切分词规则,而是利用API自身的响应来修正估算值。每次调用后,响应会包含实际处理的标记数量。我们将实际值与估算值进行比较,并在实际值更高时向上调整校准因子:
if actual_tokens > our_estimate:
calibration = max(calibration, actual_tokens / our_estimate)
calibration = min(calibration, 4.0)校准因子只会增加。高估会导致更频繁的截断,低估则会导致下次调用失败。这两种后果不对称,因此校正仅单向进行。
第三个修复是战略性而非均匀地截断。当接近预算限制时,代理会收集符合条件的工具结果,按大小排序,并优先移除最大的条目。单次大型代码执行输出可能包含50KB至200KB的日志、表格或base64编码的绘图数据。移除一个大块数据所节省的上下文空间相当于删除数十条小消息。用户消息永远不会被截断,它们包含定义分析目标的科学意图和约束条件。
这些改进共同使代理真正可用。现在,完整的50+次迭代分析可以在不出现上下文错误的情况下顺利完成。
结论
本地运行LLM处理真实代理任务时,会暴露出使用云API时容易忽略的问题。模型不仅要足够优秀,推理服务器需要精心配置,上下文窗口也需要主动管理。这两个问题都不是不可能解决的,但也不会自动发生。
第一部分中的优化会产生意想不到的协同效应。CUDA图将勉强可用的基准提升为可用;前缀缓存通过避免每次调用重读36K标记的前缀,改变了代理的交互体验;FP8键值缓存增加了内存中可容纳的上下文量;MTP则进一步提升了解码吞吐量。这些改进共同将每次迭代的时间从10-15秒缩短到大约1-3秒。
第二部分中的上下文管理改进解决了另一个问题:正确性。长期运行的科学代理需要记住自己的操作,而不仅仅是延续对话。对话历史是有用的上下文,但作为事实来源却很脆弱——它会增长、被截断,最终不得不被压缩或丢弃。特别是世界状态方法,我强烈建议任何构建面向科学或分析工作流领域特定代理的人采用。持久记录应独立于对话记录,以结构化状态的形式保存每个步骤的精确参数和结果。
这就是构建这个系统的最大收获。一个实用的代理不仅仅是带有工具的LLM,而是一个围绕基础设施的循环。模型决定下一步行动,但周围的系统决定了循环是否足够快速、稳定和可靠以完成任务。
感谢阅读,希望本文对你有所帮助!
#### 参考文献/推荐阅读
- vLLM 文档:https://docs.vllm.ai/
- vLLM 自动前缀缓存:https://docs.vllm.ai/en/latest/features/automatic_prefix_caching.html
- vLLM 推测解码:https://docs.vllm.ai/en/stable/features/speculative_decoding/
- vLLM CUDA 图形:https://docs.vllm.ai/en/stable/design/cuda_graphs/
- NVIDIA CUDA 图形:https://developer.nvidia.com/blog/cuda-graphs/
- 分页注意力机制/vLLM 论文:https://arxiv.org/abs/2309.06180
- Anthropic 代理上下文工程:https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents
- DFlash:https://z-lab.ai/projects/dflash/
- Qwen3.6–27B 模型页面:https://huggingface.co/Qwen/Qwen3.6-27B