NVIDIA Dynamo 发布会话感知智能体推理功能
Session-Aware Agentic Inference with NVIDIA Dynamo
NVIDIA Dynamo 引入会话级标识符(Session ID),将推理基础设施升级为程序感知系统,支持会话感知路由、共享 KV 缓存索引及跨 vLLM 和 SGLang 的缓存策略编排。该方案通过在工具调用边界应用背压和会话感知调度,在 SWE-bench 等测试中将智能体任务吞吐量提升了 12-16%,并提出了 KvHint 接口以实现更主动的 KV 缓存管理。
NVIDIA Dynamo 通过引入会话级标识符,实现了从请求级到程序级的推理基础设施升级,通过会话感知调度和 KV 缓存管理显著提升了智能体任务的吞吐量。
TL;DR
代理工作负载改变了推理服务器所面临的流量模式。与单轮对话不同,代理会话可能涉及大量的初始预填充(prefill)、重复的模型调用以及并行运行的子代理,且在工具执行的间隙,KV 缓存仍需驻留。
这篇技术博客详细介绍了 NVIDIA Dynamo 如何利用统一的会话级标识符,将请求级服务基础设施转变为程序感知系统,从而实现会话感知路由、共享 KV 缓存索引,以及跨 vLLM 和 SGLang 的程序化缓存迁移。
简介
代理工作负载改变了推理服务器所面临的流量形态。一个编码会话通常以大规模预填充开始,包含数万个 token 的系统提示词、工具定义和用户特定指导;此后的每一轮对话都会附加到上下文中,并由会话完整地重新发送。一个任务会分支出数十个此类调用,同时还有短生命周期和长生命周期的子代理并行运行。会话的大部分挂钟时间消耗在工具调用之间,此时上下文驻留在 KV 缓存中,而模型并未进行生成。能否高效地处理这些负载,取决于系统能驻留并复用多少上下文,以及在满足延迟目标的同时能同时容纳多少个会话。图一展示了代理工作负载与标准聊天机器人工作负载之间的这种差异。
图 1:对比了标准聊天机器人的交替用户与模型轮次,以及包含工具调用和工具响应的代理工作流。在代理工作流中,单个用户请求可以触发多次推理调用,后续步骤取决于中间结果。
大多数开源服务栈仍是为图中的模型层构建的。它们独立地路由、准入和缓存每个请求,而没有考虑到将请求与之前轮次关联起来的工具框架或会话的概念。
我们一直致力于以最优方式处理这种形态的负载。在之前的文章 《代理推理的全栈优化》 中,我们分享了 NVIDIA Dynamo 栈三个层面的工作:路由器、推理引擎和 KV 缓存管理器。在过去的几个月里,我们围绕一个单一原语统一了这些工作:通用的会话级标识符。会话级 ID(在论文和学术界通常称为程序或轨迹)将请求感知基础设施转变为程序感知基础设施,并作为额外会话感知优化的基础。
本文分四个部分介绍这一进展:
- Session_ID:Dynamo 如何从现有的框架头部识别会话和子代理,以及自定义框架如何提供相同的信息。
- 追踪与重放:会话关联的追踪如何将框架活动与推理性能连接起来,并支持离线模拟和实时基准测试。
- 会话感知调度:路由和准入控制如何考虑会话工作集,并在工具边界应用背压以减少缓存抖动。
- 共享 KV 缓存感知:实验性的共享池索引器如何让路由器在放置请求时考虑外部存储中可复用的 KV。
- 程序化 KV 缓存:会话前缀索引器和提议的 KvHint 接口如何支持跨内存层的会话感知缓存策略,其中 Dynamo 提供策略意图,而推理引擎控制执行。
通过 Session ID 识别代理会话
Dynamo 会话 ID 是针对单个智能体推理链和工具调用链的唯一且稳定的标识符。给定轨迹中的每个 LLM 请求都共享同一个 session_id。通常,子会话会携带一个 parent_session_id,以便追踪和重放工具能够在智能体与其衍生的子智能体或子任务之间建立联系。本文中的每一项优化都是对会话 ID 和父会话 ID 中所含会话信息的选择性使用。
根据调用方式的不同,有几种不同的方法可以与 Dynamo 共享稳定的会话标识符。
对编码智能体的开箱即用支持
Dynamo 可以识别当前主流编码智能体已经发出的身份标头,并将它们映射到同一个内部 agent_context:会话标头变为 session_id,而父标头(如果存在)则变为 parent_session_id。Claude Code、Codex 和 OpenCode 无需配置即可工作。
| 编码智能体 | 会话标头 | 父标头 |
|---|---|---|
| Claude Code | x-claude-code-session-id(根),x-claude-code-agent-id(子智能体) |
x-claude-code-parent-agent-id |
| Codex | thread-id |
x-codex-parent-thread-id |
| OpenCode | x-opencode-session-id |
x-opencode-parent-session-id |
我们正在与整个生态系统中的工具提供商合作,使其成为各处的默认设置,从而在用户无需更改任何内容的情况下扩展支持。Dynamo 的 CI 会跟踪每个一等工具(first-class harness)的变更。您可以在 此处 找到支持的一等 ID 的完整列表。
针对未原生发送会话标识的工具的插件
对于无法自行发出兼容标头的工具,agent-plugins 仓库 提供了小型集成,可将工具的原生会话标识转换为 x-dynamo-session-id。目前已提供三个插件:用于 Dynamo OpenAI 兼容端点的 Pi 提供程序、映射 Hermes session_id 的 Hermes 中间件,以及映射 OpenClaw sessionId 的 OpenClaw 提供程序。每个插件都在仓库的各自文件夹中提供了安装、配置和验证步骤。
自定义工具:通过一个标头选择加入
自定义工具只需一个规范标头 X-Dynamo-Session-ID 即可加入。Dynamo 会将其标准化为堆栈其余部分可读取的内部 agent_context 结构,因此下游无需知道请求来自哪个工具。如果工具支持子智能体,您还可以提供 X-Dynamo-Parent-Session-ID。以下代码块展示了任何自定义智能体如何在不采用专有 API 的情况下获得 Dynamo 的优化。
curl http://localhost:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer sk-dummy' \
-H 'x-dynamo-session-id: research-run-42:researcher' \
-d '{"model":"my-model","messages":[{"role":"user","content":"..."}]}'
工具
推理协同设计
智能体可观测性工具已经可以捕获工具层级发生的情况,但它们尚未展示推理堆栈在每个步骤下表现的统一视图。我们将这一差距视为工具 推理协同设计:为双方提供同一个请求的共享、可测量的追踪记录,该记录可用于性能可视化、重放和离线模拟,且全部由一个恒定的会话 ID 键控。此追踪旨在保持轻量级。它可以作为其他追踪收集格式(如 Harbor 的 ATIF 或 Letta 的新 Trajectory 格式)之外的额外可选元数据使用。
捕获请求追踪
要启用追踪收集,请使用 DYN_REQUEST_TRACE=1 运行您的部署。Dynamo 会自动记录每个请求的信息,默认情况下不会存储提示词、响应或工具调用内容(这些内容可选择性启用)。相反,Dynamo 会在每个响应流结束后发出一个 request_end 记录,其中包含会话字段、输出 token 计数、结束原因、KV 缓存指标、工具调用名称,以及一个包含请求输入长度和分块序列哈希的重放块,类似于 Mooncake 追踪格式。序列哈希可用于通过内置的 dynamo.replay 或我们的负载生成工具 AIPerf 等工具进行重放。
图 2:展示了 Dynamo 追踪记录的屏幕截图,其中分别列出了智能体及其工具调用的轨道。预填充(Prefill)、解码(Decode)和工具执行显示在同一时间轴上,展示了工作如何在会话中排序和重叠。
由于每条记录都带有时间戳和完成原因,我们可以查看工具调用的分布,并将它们与 KV 缓存的驱逐行为联系起来。在内部,我们使用这些技术对模型部署进行了端到端的优化,并将捕获的追踪记录用作回测数据。这些数据和重放工具中的大部分也为我们的内部自动研究工具提供了支持,使 AI 能够生成新颖的路由策略并研究主动式 KV 缓存卸载策略,我们将在下文对此进行介绍。
重放智能体追踪记录
捕获的追踪记录是一个可重用的基准。因为它记录的是智能体发送的工作负载,而不是智能体的决策,所以一次捕获可以根据需要多次重放相同的请求调度,而无需重新运行模型、工具或任何外部 API。重放过程与追踪记录一样,不包含任何内容:序列哈希值会变成合成块 ID,在不暴露原始 token 的情况下保留跨轮次和会话的提示前缀共享。
一次捕获可驱动两条路径。在离线状态下,AISimulate 会针对 Dynamo 的模拟调度器、路由器和 KV 缓存运行请求图,让你无需消耗 GPU 时间即可比较工作节点数量、服务拓扑、路由策略和缓存容量。在实时状态下,AIPerf 会针对真实 GPU 上的真实 Dynamo 端点重放相同的图,以获取最终的延迟、吞吐量和缓存数据。
会话层的路由与准入控制
KV 缓存对于高效的 LLM 推理一直很重要,但智能体推理增加了真正的复杂性:缓存位于何处、每个内存层级中可以重用多少缓存,以及系统是否主动进行优先级排序、驱逐和共享。吞吐量最终不再由原始计算能力决定,而是更多地取决于堆栈对这些问题的处理效果。
由于 Dynamo 路由器足够灵活,不仅可以处理下一个请求,我们还能够迭代基于会话感知的路由和准入控制策略,直接针对这些特性进行优化,从而提高服务智能体形态工作负载(包括 RL 后训练)的性能。其目标是尽可能多地重用 KV 缓存并避免抖动:即避免因为 HBM(最终是 DRAM)无法同时容纳每个智能体的工作集,而导致每个请求都要进行驱逐和重新预填充。
请求级路由的问题
请求级路由器(包括 Dynamo 自带的 KvRouter)一次只解决一个请求的放置问题:给定一个提示,哪个工作节点具有最多的缓存重叠。对于单轮对话来说,这是正确的单位,但对于智能体来说却是错误的单位;在高并发情况下,它会忽略请求背后智能体的两件事:
- 缓存占用激增。 在轮次之间,智能体的缓存不会被释放。当智能体在 GPU 之外运行工具调用时,它会驻留在 HBM 中并占用块。当 N 个智能体在步骤 k 并发运行时,总工作集为
N × context_k,而请求级路由器对该总量没有任何概念。它一次只能看到一个轮次的上下文。一旦工作集超过 HBM,进而超过 DRAM,引擎就会开始驱逐活跃会话的缓存,导致后续的每一轮都需要支付完整的重新预填充成本,而不是命中缓存。 - 无工具边界背压。当 worker 超负荷时,请求级路由器的唯一手段是取消或排队处理中的请求。这两种方式都比替代方案更差:在会话即将进入下一轮次(即即将空闲)之前的暂停点处推迟会话。
会话感知准入控制
我们首个代理感知路由策略将 ThunderAgent 论文(Kang et al., 2026)中的调度器移植到了 Dynamo 的 KV 感知路由器之上。它使用 Rust 编写,直接拥有一个 KvRouter,而不是作为额外的代理跳转位于其前端,并且它通过追踪每次响应的真实 prompt_tokens + completion_tokens 来代替根据原始字节估算 Token 数量。由于它是作为原生 Dynamo 插件编写的,你可以获取其 源代码 并根据你的工作负载形态进行适配,而无需更改 Dynamo 底层的路由器代码。
调度器按 program_id(会话 ID)对请求进行分组,并使每个程序经历两个独立的状态:(REASONING | ACTING) × (ACTIVE | PAUSED)。程序在工具边界处进入 ACTING。在内存压力下,调度器会在逻辑上暂停 ACTING 程序。这里没有解码抢占;处理中的轮次会被允许完成,从而释放引擎以驱逐其 KV 块。如果 worker 没有剩余的 ACTING 程序可供暂停,它会转而标记最小的 REASONING 程序,以便在其下一个工具边界处暂停。当 worker 再次拥有余量时,调度器会优先恢复 Token 数最小的已暂停程序,将它们装箱(bin-packing)回阈值之下。
控制循环
暂停和恢复均由每个 worker 的一个指标驱动:利用率,即 worker 的 KV 池中被其分配程序的活跃集所占用的比例:
U_worker = (sum of token weights for ACTING programs on worker) / (worker's KV pool capacity)
三个阈值(显示为默认值)将该数值转化为一个控制循环:
- U ≥ 0.95(暂停阈值):worker 处于超额订阅状态。该节拍(tick)会优先暂停最小的 ACTING 程序,直到 U 降回 0.80(暂停目标)。
- 0.80 ≤ U < 0.95(软降级区间):程序尚未被暂停,但会受到 -2.0 的优先级惩罚,作为在需要硬暂停之前的早期背压。
- 仅在 U ≤ 0.85 时触发恢复:即暂停阈值减去 0.10 的恢复滞后(resume-hysteresis),这是一个低于暂停线的缓冲区,可防止循环在阈值处反复在暂停和恢复之间波动。
恢复本身是一个装箱过程:已暂停的程序按 Token 数量升序排列,调度器按从小到大的顺序准入它们,直到下一个程序会使利用率再次超过阈值(计入每个程序的固定缓冲区)。恢复的请求会获得一秒的优先级提升,这样它们就不会被卡在新的准入请求之后;同时,30 分钟的强制恢复上限保证了没有任何程序会无限期地处于饥饿状态。
结果
在中高并发下,我们观察到吞吐量有显著提升。这种差异直接体现在吞吐量上。在 SWE-bench 上,在单个 8xH100 节点上运行两个 TP4 MiniMax-M2 副本时,程序感知调度比单纯的 KV 路由提高了约 12-16% 的吞吐量,差距源于请求级路由无法避免的重新预填充(re-prefill)工作。
同样的策略在智能体强化学习(agentic RL)的 rollout 阶段也展现出了类似的巨大提升。在 8xH20-3e 上使用 Qwen3-Coder-30B-A3B-Instruct 运行 Uni-Agent SWE-Bench,并对比 VERL 默认的 Global LB,我们的策略在低并发(CC32-128)下表现基本持平,此时无需暂停任何任务。在中等并发(CC192-256)下,它开始领先,模型 token 吞吐量比 Global LB 高出 11.0-14.6%;而在高并发(CC384-512)下,Global LB 的吞吐量急剧下降,而我们的策略仍在持续扩展。在整个测试过程中,它将前缀缓存命中率保持在 94.5% 以上。
图 3:比较了 VERL Global LB、Dynamo Python TA 和 Dynamo 原生会话感知调度器在不同智能体准入并发水平下的模型 token 吞吐量。在高并发下,原生调度器保持了三者中最高的吞吐量,而 Global LB 的吞吐量则急剧下降。
路由器中的共享 KV 缓存感知
一旦工作集超过 HBM 容量,KV 缓存的放置就会变得更加困难。缓存并不会消失,而是会移动到 CPU 内存中,进而移动到 Mooncake 或 NIXL 支持的存储等共享存储中。到目前为止,Dynamo 路由器仅能感知 GPU 和原生框架 CPU 卸载的内存。当 KV 移动到外部存储时,Dynamo 无法将其纳入成本函数中。为了解决这个问题,我们与 Mooncake 团队合作,实现了从 Mooncake Store 原生发出 KV 事件,并实现了一个新的实验性共享池索引器,它与 Dynamo 极速的 FlashIndexer 协同工作。当 KV 从 HiCache(SGLang 的原生 CPU 卸载机制)移动到 Mooncake 时,SGLang 会发出移除事件,Mooncake 会发出存储事件。只有当工作器并行布局所需的所有物理对象都存在时,页面才会被计为命中,并且信用额度会延伸到其设备本地前缀之外。
在请求时,Dynamo 将提示词哈希为 SGLang KV 页面,并将每个逻辑页面扩展为工作器 TP/PP 布局、K/V 张量和可选后端标签所隐含的物理 Mooncake 对象。Mooncake 发布有序的 Store 和 Remove 事件,Dynamo 将其消费到全局共享池索引中,并保守地仅在每个所需的物理对象都存在时才将页面视为可重用。索引将共享命中作为连续页面范围返回,而不是在路由热路径上探测 Mooncake。FlashIndexer 独立计算每个候选工作器的原生设备、CPU 和磁盘前缀重叠。对于设备驻留前缀深度为 D 的工作器,路由器仅在 >= D: shared_beyond_device = |shared_hit_ranges ∩ [D, request_pages)| 位置计入共享命中。这防止了 GPU 本地命中被重复计算,同时允许具有较短本地前缀的工作器获得 Mooncake 中已可用后缀的信用。最终的放置分数结合了加权原生层重叠、可配置的共享池信用和实时工作器负载。
迈向程序化 KV 缓存
归根结底,智能体工作负载的高性能推理需要会话感知的 KV 块管理。为了预测此类工作负载中给定 KV 缓存块的优先级值,我们需要来自路由器和引擎的互补信息。路由器了解跨工作器的会话生命周期和重用模式;引擎则单独了解其持有的 KV 缓存状态。因此,为了让引擎(vLLM、SGLang)执行最大程度知情的缓存策略,(1) Dynamo 应表达策略意图,(2) 引擎应根据确切的缓存状态解析该意图并执行。
这就是 programmatic KV cache 的核心理念。本质上,Dynamo 路由器提供了“大脑”,用于计算智能的、感知会话/负载/缓存的 KV 移动策略,而引擎则“执行”Dynamo 要求的移动操作。因此,我们一直在为 Dynamo 和 vLLM/SGLang 开发相关功能:即 Dynamo 计算并发出 KV 策略的机制,以及引擎中将 Dynamo 基于会话的策略降级为具体的引擎内 KV 移动所需的顶层抽象。
路由器驱动的 KV 提示策略
Dynamo 的程序化 KV 缓存移动由一个狭窄的、由路由器发起的提示接口(“KvHint”)驱动,该接口使 Dynamo 能够将 KV 缓存意图传递给 vLLM/SGLang。这避免了对引擎原生调度器和缓存管理器的侵入式修改。(SGLang 设计,vLLM 设计)。
编排器掌握着请求局部策略无法看到的信息。哪些会话仍然活跃,哪些 Token 范围是共享前缀,哪些是唯一尾部,工具调用间隙会持续几毫秒还是几分钟,子代理何时开启和关闭。这些知识属于路由器层,因为编排契约和会话生命周期已经存在于那里,它们应该保留在那里。引擎的工作是提供机制,并让路由器提供意图。
该提案是一组路由器可以向引擎发出的狭窄的软提示。以下是潜在 KvHints 及其功能的分类摘要:
| KvHint 名称 | 功能 | 使用示例 |
|---|---|---|
| Share | 通过 p2p 传输将缓存的前缀从一个 worker 移动到另一个 worker | 新的引擎 worker 从现有 worker 的缓存中预热其缓存。 |
| Prefetch | 在需要之前,将 KV 从更高层(例如 DRAM)获取到更低层 | 在子代理即将完成时,将主代理的上下文重新加载到 HBM 中。 |
| Demote | 将 KV 从更低层推送到更高层。 | 长工具调用或暂停的轨迹。 |
| Pin | 将高价值的 KV 缓存固定在其当前层上,并设置有限的 TTL | 固定所有请求共享的系统缓存。 |
| Retain | 使 KV 块在缓存压力下更有(或更少)可能被保留。 | 在工具调用期间保留子代理的 KV 块。 |
每个提示都携带会话 ID,因此接收 KV 的层级知道它属于哪个会话,并可以将其作为一个单元进行分组、放置和回收,而不是作为匿名块。这在较冷的层级中最为重要,因为持有主机内存或磁盘的存储后端否则只能看到不透明的哈希值,无法区分活跃轨迹的前缀和已完成轨迹的前缀。引擎可以裁剪、推迟或忽略任何提示,而不发送提示的工作负载将表现得与今天完全一样。
Share 已经在 HiCache 中有了可用的实现,且只需极少的调度器钩子;而 Prefetch 和 Demote 则设计为在 Share 落地后基于该机制构建。
通过会话前缀索引器启用 KV 提示策略计算
为了让 Dynamo 在会话级别组合并执行这些 KvHint 策略,它必须拥有一个持续更新的结构,该结构可以在 KV 块、它们的会话身份以及它们在会话中的位置谱系之间进行双向映射。为此,我们实现了会话前缀索引器(Session-Prefix Indexer),这是一种基数树结构,其每个分支代表一个唯一的会话(或从另一个会话分支出来的子代理会话)。
Session-Prefix Indexer(会话前缀索引器)持续从推理引擎 KV 事件(存储/移除)以及 Dynamo 路由器的主要索引器(“FlashIndexer”)的前缀缓存命中中获取数据。因此,通过 FlashIndexer 和 Session-Prefix Indexer,Dynamo 路由器能够累积掌握 (1) 推理引擎工作节点的块所有权,以及 (2) 这些块的会话归属关系。这种信息丰富的视图可供各种 KV 提示策略使用,这些策略需要有关 KV 块的工作节点位置、活跃会话生命周期和/或引擎工作负载的信号。
有了这些索引器,我们正在 Dynamo vLLM/SGLang 上针对智能体工作负载数据集积极试验 KvHint 策略。
图 4:Session-Prefix Indexer 在 Dynamo 路由器内跟踪共享前缀、会话血缘和 KV 块位置。来自推理工作节点的 KV 事件会更新此视图,从而支持路由决策和向 vLLM 或 SGLang 发送可选 KV 提示的策略。
展望未来
高效的智能体推理依赖于管理整个会话生命周期内的上下文。Dynamo 的会话 ID 将工具行为与路由、准入控制和缓存管理连接起来,使服务栈能够共享智能体不断演进的工作集视图。
会话关联的追踪和重放使策略变得可衡量,而具备会话感知能力的准入控制通过在工具边界施加背压,减少了缓存抖动。在本文测试的 SWE-bench 配置中,程序感知调度比单纯的 KV 感知路由将吞吐量提高了约 12–16%。实验性的共享池索引器将路由可见性扩展到了外部 KV 存储。
Session-Prefix Indexer 和提议的 KvHint 接口旨在实现主动式缓存迁移:Dynamo 提供策略意图,而引擎保留对执行的控制权。关键结论是,缓存重用和会话并发必须协同优化。在并发增长时,保持有用上下文在多轮对话中可用并避免重复预填充,是高效服务更多智能体会话的核心。
文章 Session-Aware Agentic Inference with NVIDIA Dynamo 最早出现在 PyTorch。
来源:PyTorch Blog · pytorch.org