Session-Aware Agentic Inference with NVIDIA Dynamo

TL;DR · AI 摘要
Session-Aware Agentic Inference with NVIDIA Dynamo - PyTorch Featured projects TL;DR Agentic workloads change the traffi...
核心要点
- 主题聚焦:Session-Aware Agentic Inference with NVIDIA Dyna
- 来源:PyTorch Blog,建议结合原文判断细节。
- AI 分析暂不可用,本条为保底评分与摘要。
基于 NVIDIA Dynamo 的会话感知智能代理推理 - PyTorch
亮点项目
TL;DR
智能代理工作负载改变了推理服务器所看到的流量特征。与单轮对话不同,代理会话可能包含大量初始预填充内容、重复模型调用以及并行运行的子代理,且在轮次之间执行工具时,KV 缓存会持续驻留。
本文详细说明 NVIDIA Dynamo 如何通过统一的会话级标识符,将请求级服务基础设施转变为程序感知系统,从而实现会话感知路由、共享 KV 缓存索引,以及跨 vLLM 和 SGLang 的程序化缓存迁移。
引言
智能代理工作负载改变了推理服务器所看到的流量形态。一个编码会话通常以大量预填充内容开始,包含数万个系统提示、工具定义和用户特定指导的 token,之后每次轮次都会将内容追加到上下文中并重新发送完整会话。一个任务可能扩展为数十次调用,同时并行运行短时和长时子代理。会话的大部分墙钟时间都消耗在工具调用之间,此时上下文驻留在 KV 缓存中却无任何生成操作。高效服务这些负载的关键在于系统能保留并重用多少上下文,以及在满足延迟目标的同时能同时处理多少会话。这种智能代理工作负载与标准聊天机器人之间的对比见图 1。
Figure 1:对比标准聊天机器人的用户与模型轮次交替模式,与包含工具调用和响应的智能代理工作流。在智能代理工作流中,单个用户请求可能触发多次推理调用,后续步骤依赖中间结果。
大多数开源服务框架仍基于图中所示的模型构建。它们独立路由、接纳和缓存每个请求,完全不考虑将请求与先前轮次关联的框架或会话概念。
我们一直在努力实现对这种负载形态的最优服务。在之前的博客《面向智能代理推理的全栈优化》中,我们分享了 NVIDIA Dynamo 栈的三个层级(路由器、推理引擎和 KV 缓存管理器)的协同工作。过去几个月,我们围绕一个核心原语统一了这些工作:通用会话级标识符。会话级 ID(在论文和学术界常称为 program 或 trajectory)将请求感知的基础设施转变为程序感知的基础设施,为更多会话感知优化奠定基础。
本文将从以下四个部分介绍进展:
- Session_ID:Dynamo 如何从现有框架头信息中识别会话和子代理,以及自定义框架如何提供相同信息。
- 追踪与回放:会话关联的追踪如何将框架活动与推理性能关联,支持离线模拟和实时基准测试。
- 会话感知调度:路由和准入控制如何考虑会话工作集,在工具边界应用反压以减少缓存抖动。
- 共享 KV 缓存感知:实验性共享池索引器如何使路由器在分配请求时考虑外部存储中的可重用 KV。
- 程序化 KV 缓存:Session-Prefix 索引器和提议的 KvHint 接口如何支持跨内存层级的会话感知缓存策略,Dynamo 提供策略意图,推理引擎控制执行。
通过会话 ID 识别代理会话
Dynamo 会话 ID 是单个代理推理链和工具调用的唯一稳定标识符。给定轨迹中的每个 LLM 请求共享相同的 session_id。通常,子会话会携带 parent_session_id,这样追踪和回放工具可以建立代理与子代理或子任务之间的关联。本文中提到的所有优化都是 session 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 |
我们正在与生态系统中的各种 harness 提供商合作,使这种支持成为默认选项,因此无需用户更改任何内容即可扩展支持。Dynamo 的 CI 会跟踪每个一级 harness 的变化。你可以在 [此处](#) 找到支持的一级 ID 的完整列表。
用于不原生发送会话身份的 harness 的插件
对于无法自行发出兼容头的 harness,agent-plugins 仓库提供了小型集成,将 harness 的原生会话身份转换为 x-dynamo-session-id。目前有三种可用:用于 Dynamo 的 OpenAI 兼容端点的 Pi 提供商、映射 Hermes 会话 ID 的 Hermes 中间件,以及映射 OpenClaw sessionId 的 OpenClaw 提供商。每个插件都在仓库中的独立文件夹中提供自己的安装、配置和验证步骤。
自定义 harness:通过一个头进行可选加入
自定义 harness 通过一个标准头 X-Dynamo-Session-ID 进行可选加入。Dynamo 会将其规范化为内部 agent_context 结构,其余堆栈读取该结构,因此下游无需知道请求来自哪个 harness。如果 harness 支持子代理,还可以提供 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":"..."}]}'Harness ↔ 推理联合设计
可观测性工具已经能够捕获代理层(harness level)的执行情况,但尚未提供对每个步骤下推理堆栈整体表现的统一视图。我们把这种差距称为“harness ↔ 推理协同设计”:为双方提供一个共享的、可度量的相同请求追踪记录,该记录可用于性能可视化、回放和离线模拟,所有操作均通过固定的会话ID(session ID)进行关联。该追踪记录应保持轻量化,可作为其他追踪格式(如Harbor的ATIF或Letta的新Trajectory格式)的可选元数据补充。
捕获请求追踪
要启用追踪收集,请使用DYN_REQUEST_TRACE=1运行部署。Dynamo会自动记录每个请求的信息,默认情况下不会存储提示词、响应或工具调用内容(这些功能可选择启用)。取而代之的是,Dynamo在每次响应流结束后会发出一个request_end记录,包含会话字段、输出token数量、完成原因、KV缓存指标、工具调用名称,以及一个包含请求输入长度和每块序列哈希值的回放块,格式与Mooncake追踪类似。这些序列哈希值可通过内置的dynamo.replay工具或我们的负载生成工具AIPerf进行回放。
图2:展示了dynamo追踪的屏幕截图,包含代理和工具调用的独立追踪轨道。预填充、解码和工具执行出现在同一时间线上,展示了会话期间工作如何排序和重叠。
由于每个记录都包含时间戳和完成原因,我们可以观察工具调用的分布情况并将其与KV缓存驱逐行为关联。在内部,我们已使用这些技术对模型部署进行端到端优化,将捕获的追踪数据作为回测数据。这些数据和回放工具也支撑着我们的内部自动研究框架(autoresearch harnesses),使AI能够生成新颖的路由策略,并研究主动的KV缓存卸载策略,相关内容将在下文详细说明。
重放代理追踪
捕获的追踪记录是一种可重复使用的基准。由于它记录的是代理发送的工作负载而非代理的决策,因此一次捕获可以按需重复执行相同的请求调度,无需重新运行模型、工具或任何外部API。与追踪记录一样,重放过程同样保持内容中立:序列哈希值会转化为合成块ID,这些ID在跨轮次和会话时保留提示词前缀共享特性,同时不暴露原始token。
一次捕获可驱动两条路径。离线时,AISimulate会将请求图与Dynamo的模拟调度器、路由器和KV缓存进行对比,使您能在不消耗GPU资源的情况下比较工作线程数量、服务拓扑、路由策略和缓存容量。在线时,AIPerf会将相同图在真实GPU上的真实Dynamo端点进行回放,最终获取延迟、吞吐量和缓存指标数据。
会话层的路由与准入控制
KV缓存始终对高效LLM推理至关重要,但代理推理增加了真实复杂性:缓存存储位置、每个内存层级可复用的缓存比例,以及系统是否主动进行优先级排序、驱逐和共享。最终吞吐量的决定因素不再仅仅是原始计算能力,而是堆栈对这些问题的回答效果。
因为 Dynamo 路由器的灵活性足以应对除下一个请求之外的更多场景,我们得以迭代改进会话感知路由和准入控制策略,直接针对这些特性进行优化,从而提升处理智能体形状工作负载(包括 RL 后训练)的性能。目标是尽可能复用 KV 缓存,避免缓存抖动:并非每个请求都进行驱逐和重新预填充,因为 HBM 最终 DRAM 无法同时容纳所有智能体的工作集。
请求级路由的问题
Dynamo 默认的 KvRouter(请求级路由器)逐个解决请求的放置问题:给定一个提示,哪个工作节点的缓存重叠度最高。这在单次交互中是正确的单位,但对智能体来说却是错误的单位。在高并发场景下,它会忽略请求背后智能体的两个关键特性:
- 缓存占用激增。在交互之间,智能体的缓存不会被释放。它会停留在 HBM 中,当智能体在 GPU 之外运行工具调用时仍占用内存块。当 N 个智能体在第 k 步并发运行时,总工作集大小为 N × context_k,而请求级路由器完全无法感知这种聚合。它每次只能看到单次交互的上下文。一旦工作集超过 HBM 容量,甚至 DRAM 容量,引擎开始驱逐活跃会话的缓存,每个后续交互都需要完整重新预填充而非缓存命中。
- 无工具边界反压。当工作节点超载时,请求级路由器只能通过取消或排队在飞行请求来调节。这两种方式都比替代方案更差:在智能体下一次交互即将到达前的暂停点延迟会话,此时智能体本来就会处于空闲状态。
会话感知准入控制
我们的首个智能体感知路由策略将 ThunderAgent 论文(Kang 等,2026)中的调度器移植到 Dynamo 的 KV 感知路由器之上。该调度器用 Rust 编写,直接拥有 KvRouter 而非作为额外代理层位于其前端,且通过响应中的实际 prompt_tokens + completion_tokens 进行统计,而非通过原始字节估算 token 数量。由于这是作为原生 Dynamo 插件实现的,你可以直接使用其源代码并适配到自己的工作负载形状,而无需修改 Dynamo 的底层路由器代码。
调度器按 program_id(会话 ID)对请求进行分组,并将每个程序通过两个独立状态:(REASONING | ACTING) × (ACTIVE | PAUSED)。程序在工具边界进入 ACTING 状态。在内存压力下,调度器会逻辑上暂停 ACTING 程序。没有解码抢占;飞行中的交互被允许完成,从而释放引擎驱逐其 KV 块。如果工作节点没有剩余 ACTING 程序可暂停,则会标记最小的 REASONING 程序在其下一个工具边界暂停。当工作节点再次有空余时,调度器会优先恢复最小 token 数量的暂停程序,将其重新打包至阈值以下。
控制循环
暂停和恢复均由每个工作节点的一个指标驱动:利用率(worker 的 KV 池被分配程序的工作集占用比例):
U_worker = (工作节点上 ACTING 程序的 token 权重总和) / (工作节点的 KV 池容量)三个阈值(默认值如上)将该指标转化为控制循环:
- U ≥ 0.95(暂停阈值):工作节点已过载。调度器优先暂停最小的 ACTING 程序,直到 U 降至 0.80(暂停目标)。
- 0.80 ≤ U < 0.95(软降级区间):程序尚未暂停,但会因硬暂停前的早期反压而受到-2.0的优先级惩罚。
- 仅当U ≤ 0.85时才会触发恢复:这是暂停阈值减去0.10的恢复滞后,形成一个低于暂停线的缓冲区,防止在阈值处频繁切换暂停和恢复状态。
恢复本身是一个装箱过程:暂停的程序按令牌数量升序排列,调度器按从小到大顺序恢复程序,直到下一个程序恢复会导致利用率重新超过阈值,同时考虑每个程序的固定缓冲区。恢复的请求会获得1秒的优先级提升,以避免被新入队的请求阻塞,而30分钟的强制恢复上限可确保任何程序不会无限期饥饿。
结果
在中等至高并发场景下,我们观察到吞吐量显著提升。这种差异直接体现在吞吐量指标上。在SWE-bench测试中,单个8xH100节点运行两个TP4 MiniMax-M2副本时,基于程序感知的调度相比KV路由单独使用,吞吐量提升了约12-16%,差距主要来自请求级路由无法避免的重预填充工作。
同样的策略在智能体强化学习部署中也表现出类似显著提升。在8xH20-3e节点上运行Uni-Agent SWE-Bench,使用Qwen3-Coder-30B-A3B-Instruct对抗VERL默认的全局负载均衡器时,我们的策略在低并发(CC32-128)场景下与全局负载均衡器表现相当(无需暂停任何任务)。在中等并发(CC192-256)场景下,我们的策略实现11.0-14.6%的模型令牌吞吐量优势,而在高并发(CC384-512)场景下,全局负载均衡器吞吐量急剧下降,而我们的策略持续扩展。在整个测试范围内,前缀缓存命中率始终保持在94.5%以上。
图3:对比VERL全局负载均衡器、Dynamo Python TA和Dynamo原生会话感知调度器在不同代理准入并发级别下的模型令牌吞吐量。在高并发场景下,原生调度器保持最高的吞吐量,而全局负载均衡器吞吐量急剧下降。
路由器中的共享KV缓存感知
当工作集超出HBM容量时,KV缓存的放置变得更为复杂。缓存并未消失,而是转移到CPU内存,再进一步转移到Mooncake或NIXL等共享存储。此前Dynamo路由器仅能感知GPU和原生框架的CPU卸载内存。当KV转移到外部存储时,Dynamo无法将其纳入成本函数计算。为解决此问题,我们与Mooncake团队合作,实现从Mooncake存储原生发出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)引擎应将该意图与精确的缓存状态对齐并执行。
这就是程序化KV缓存的核心思想。本质上,Dynamo路由器提供计算智能、会话/负载/缓存感知的KV移动策略的“大脑”,而引擎则“执行”Dynamo所规定的移动操作。因此,我们正在为Dynamo和vLLM/SGLang开发功能:Dynamo计算+发出KV策略的机制,以及引擎中将Dynamo基于会话的策略转化为具体引擎内KV移动所需的一等抽象。
路由器驱动的KV提示策略
Dynamo的程序化KV缓存移动由一个狭窄的、由路由器发起的提示接口("KvHint")驱动,这使得Dynamo能够将KV缓存意图传递给vLLM/SGLang。这避免了对引擎原生调度器和缓存管理器进行侵入式修改。(SGLang设计,vLLM设计)
调度器持有请求本地策略无法看到的信息。哪些会话仍然活跃,哪些令牌范围是共享前缀还是唯一尾部,工具调用间隙将持续毫秒还是分钟,子代理何时开启和关闭。这些知识应属于路由器层,因为Harness合同和会话生命周期已经存在于该层,且应保留在该层。引擎的工作是暴露机制,让路由器提供意图。
该提案是路由器可以向引擎发出的一组狭窄的软提示。以下是潜在KvHints及其功能的部分分类:
KvHint名称
功能
示例用法
Share
通过点对点传输将缓存前缀从一个工作者转移到另一个工作者
新引擎工作者从现有工作者的缓存中预热其缓存
Prefetch
在需求之前从更高层级(如DRAM)获取KV到更低层级
重新加载主代理的上下文到HBM,当子代理接近完成时。
降级
将较低层级的KV推送到较高层级。
长时间的工具调用,或暂停的轨迹。
固定
将高价值的KV缓存固定在其当前层级,持续一段时间(TTL)。
固定系统缓存,该缓存被所有请求共享。
保留
在缓存压力下,偏向保留(或减少保留)KV块的可能性。
在工具调用期间保留子代理的KV块。
每个提示都携带会话ID,因此接收KV的层级可以知道该KV属于哪个会话,并能将其作为整体进行分组、放置和回收,而非匿名块。这一点在较冷的层级中尤为重要,因为存储后端(如主机内存或磁盘)通常只能看到不透明的哈希值,无法区分正在进行的轨迹前缀和已完成的轨迹。引擎可以剪切、延迟或忽略任何提示,且不发送提示的工作负载的行为与当前完全一致。
共享功能已有一个基于HiCache的实现,仅需最小的调度器钩子,而预取和降级功能则设计为在共享功能上线后利用该机制。
通过会话前缀索引器启用KV提示策略计算
为了在会话级别上组合和执行这些KvHint策略,Dynamo必须拥有一个持续更新的结构,该结构可以在KV块、其会话标识以及其会话内的位置谱系之间进行双向映射。为此,我们实现了会话前缀索引器,这是一个基数树结构,其中每个分支代表一个唯一的会话(或从其他会话分支出的子代理会话)。
会话前缀索引器持续从推理引擎的KV事件(存储/删除)以及Dynamo路由器主索引器("FlashIndexer")的前缀缓存命中中获取数据。因此,通过FlashIndexer和会话前缀索引器,Dynamo路由器可以获取(1)推理引擎工作节点的块所有权信息,以及(2)这些块的会话成员信息。这种信息丰富的视图可以被各种需要KV块工作节点位置、活跃会话生命周期和/或引擎工作负载信号的KV提示策略使用。
借助这些索引器,我们正在与Dynamo vLLM/SGLang在智能体工作负载数据集上积极试验KvHint策略。
图4:会话前缀索引器跟踪Dynamo路由器内的共享前缀、会话谱系和KV块位置。来自推理工作节点的KV事件更新此视图,支持路由决策和向vLLM或SGLang发送可选KV提示的策略。
展望未来
高效的智能体推理依赖于在整个会话生命周期中管理上下文。Dynamo的会话ID将Harness行为与路由、准入控制和缓存管理连接起来,使服务堆栈能够共享智能体工作集演变的全局视图。
与会话关联的追踪和重放使策略可度量,而基于会话的准入控制通过在工具边界应用反压来减少缓存抖动。在本测试的SWE-bench配置中,程序感知调度相比仅基于KV感知的路由,吞吐量提高了约12-16%。实验性共享池索引器将路由可见性扩展到外部KV存储。
会话前缀索引器和提出的KvHint接口共同推动了主动缓存迁移的实现:Dynamo提供策略意图,而引擎保留执行控制权。关键结论是缓存复用与会话并发性必须协同优化。在保持多轮对话中有效上下文可用性、避免并发量增加时重复预填充,是高效服务更多智能体会话的核心所在。
/post-content
/inner-wrap