Zaifeng Pan, Ajjkumar Patel, Zhengding Hu, Yipeng Shen, Yue Guan, Wan-Lu Li, Lianhui Qin, Yida Wang, Yufei Ding (UCSD & AWS) | 2025-07 | arXiv:2507.07400 Category: framework | Tags: prefix-caching, kv-cache-management, multi-agent, sglang, scheduling Read: 2026-04-18
KVFlow replaces SGLang 的 LRU 前缀缓存驱逐策略,改为 workflow-aware 的 "steps-to-execution" 优先级 + 完全重叠的 CPU→GPU 预取,在多智能体(multi-agent)工作流场景下相比 SGLang+HiCache 实现 1.83–2.19× 端到端加速。
现代 agentic workflow (MetaGPT、AutoGen、AFlow、PEER、GPTSwarm 等) 把多个 "角色固定、prompt 很大" 的 agent 按图(graph)组织起来,反复迭代调用 LLM。每个 agent 的 system prompt(角色 + few-shot example)常在 1–3k+ tokens,是 prefix caching 的天然受益者。但现有 serving 系统(SGLang/vLLM)使用 LRU 驱逐 —— 当 GPU 显存不够时,淘汰"最近最少使用"的 KV 节点。作者观察到这在 agentic workflow 中是反向信号:一个刚执行完、很快要再次执行 的 agent (例如 PEER 里 Executor → Expresser 的下一跳),LRU 恰恰会认为它"不新鲜"而优先驱逐,下一步立刻发生 cache miss。
KVFlow 的思路是把 workflow 拓扑信息 下推到 serving 后端。它引入 Agent Step Graph 抽象:每个节点是一次 agent 调用,边表示依赖;每个节点有一个 step aggregation function (max(...)+1 表示 join,min(...)+1 表示 conditional branch),从而对任意 DAG / 环 / 条件分支都能计算出每个 agent 的 steps-to-execution (STE) —— 最早可能被执行的步数距离。驱逐时,radix tree 上每个 KV 节点继承其"所属 agent"的 STE,STE 越小越靠近被重用,优先级越高,反之最先驱逐。因为多个 agent 可共享前缀(树形结构),共享节点取所有子 agent STE 的 最小值。同时,所有 agent 的 dynamic 后缀无条件取最高驱逐优先级。
在此基础上作者还给了两个配套机制:(1) proactive prefetching — 根据 Step Graph 预测下一步将被激活的 agent,在当前 agent 还在 GPU 上做 forward/decode 时,用后台线程把它的 KV 从 CPU 通过 PCIe 预取到 GPU,占用的是另一个方向的全双工带宽;(2) status-aware scheduling — 每个 cache 节点有 {in-GPU, in-CPU, loading, offloading} 四态,调度器跳过"仍在 loading"的请求去执行 ready 的请求,从而 GPU 不空转。实现基于 SGLang v0.4.4,对 radix tree 的驱逐函数和调度器做了扩展;前端通过 sgl.function 截获 HTTP 请求,把 workflow metadata 注入后端。评测在 A10G (Llama-3.1-8B, PCIe Gen1 2 GB/s) 和 H100 (Qwen2.5-32B, PCIe Gen5 64 GB/s) 上:单工作流 8192 fixed / 32 dynamic / 32 output 时相对 SGLang+HiCache 1.83×、相对纯 GPU SGLang 2.91×;64 并发 × 1024 fixed prompt 时相对 HiCache 2.19×。
max+1 / min+1 的步聚合函数可以把 DAG、cycle、conditional branch 用同一种抽象表达,不需要为每种 workflow frontend 专门定制。2024–2025 agentic workflow (MetaGPT / AutoGen / AFlow / LangGraph) 已成为 LLM 应用层的主流范式,但 serving 基础设施仍是"单请求 / stateless 模型"假设。Prefix caching 的 low-hanging fruit 已经被 SGLang (RadixAttention)、vLLM (automatic prefix caching) 吃掉;下一波优化要开始利用更高层的语义信息(workflow 拓扑、agent 依赖)来做 KV 生命周期管理。KVFlow 代表 "serving 层开始感知 application-layer graph" 的新方向,和 Parrot/Autellix 的调度级 workflow 感知是同期工作,但 KVFlow 聚焦在 prefix cache 这一被前人忽略的维度。
现有路径:
为何不可直接把所有 agent prompt 常驻 GPU?
当 4 个 agent × 各 8192 token × Llama-3.1-8B (32 层, 8 KV heads, 128 dim, FP16) ≈ 4 × 8192 × 32 × 8 × 128 × 2 × 2 B ≈ 4.3 GB 只算 prefix;实际 workflow 有 10+ agents + dynamic 后缀 + batch,24 GB A10G 直接爆。
为何不可 "刚用完就 evict"(MRU 反着来)?
Cyclic workflow 里下一步就要再用;MRU 会直接干掉热数据。
为何不可用 LFU?
首次执行所有 agent 频次相同,LFU 退化为 FIFO;且 workflow 换 phase 时频次已经过时。
为何不可用 "access-time-weighted LRU" (例如 priority = age × freq)?
仍然看的是"过去",对"未来"无预测能力;agent 再次激活的时刻由 workflow 拓扑决定,而不是由历史访问频次决定。
为何必须做到 KV node 粒度而不是 agent 粒度?
多个 agent 共享前缀时(例如 PEER 的 4 个 agent 都以同一条 system preamble 开头),在 agent 粒度上会产生冲突:"Planner 想驱逐前缀"但"Reviewer 想保留"。Radix tree 天然把共享前缀合并成同一 node,必须在 node 层做优先级聚合(作者选择了 min 规则)。
| 替代方案 | 能预测未来? | 支持 branch/cycle? | 共享前缀一致性? | 实现复杂度 |
|---|---|---|---|---|
| LRU | ❌ | N/A | N/A(冲突) | 低 |
| LFU | ❌ | N/A | N/A | 低 |
| MRU | ❌(反向误判) | ❌ | ❌ | 低 |
| Agent 级 STE | ✅ | ✅ | ❌(冲突) | 中 |
| KV-node 级 STE + min 聚合 ✅ | ✅ | ✅ | ✅ | 中高 |
"过去的访问模式在 agentic workflow 里是随机的,但未来的访问顺序是 application 自己给出的 DAG"。 与其让系统从时间序列里反推谁会被用到,不如让 application 直接把 workflow graph 递给后端。这就像公交调度——如果司机知道下一站是哪个站,就能提前开门;LRU 相当于让司机看后视镜猜下一站在哪。
整个方案能 work 的关键不是 "STE 这个概念"(这是直觉的),而是 "KV-node 级 min 聚合 + 状态机 (in-GPU/CPU/loading/offloading) 与 radix tree 驱逐/调度器的原子集成"。具体地:
复现这两个机制需要深入理解 SGLang 的 radix tree + scheduler 内部实现;这是 3–4 周 dev effort 而非 "加一个 priority key"。
KVFlow 强制绑定了:
sgl.function)——必须能在 frontend 识别 agent 边界并注入 HTTP metadata;换到其他框架需要手动适配。
What it shows: 一个 4-agent 循环工作流(Planner → Executor → Expresser → Reviewer → Planner …)的时间轴。在 timestamp 13 Executor 执行更新自己的 KV 时,LRU 决定驱逐 Expresser 的 cache;timestamp 14 Expresser 立刻要执行,发生 cache miss 并付出完整 prefill 代价。
Why it matters: 这是全文 motivation 的"反直觉信号"证据——在 agentic workflow 中,access recency 与 future reuse 负相关,不是正相关。
Detailed description: 图分成两行时间线:上行是 agent 执行序列(Planner @ t=10, Executor @ t=13, Expresser @ t=14…),下行是 KV cache 状态(Expresser KV 在 t=13 被画叉表示驱逐,t=14 时 prefill 延迟被高亮为红色表示 cache miss 代价)。箭头从 Executor 指向被驱逐的 Expresser 说明"LRU 做决策的依据——Expresser 最久没被用"。

What it shows: 两种不同的 workflow 拓扑以及每个 agent 的 steps-to-execution。上图 (join) 里 Expresser 需要等 Executor1 和 Executor2 都完成,所以 $\text{STE}(\text{Expresser}) = \max(E_1, E_2) + 1$;下图 (branch) 里任一 Executor 完成即可,所以 $\text{STE}(\text{Expresser}) = \min(E_1, E_2) + 1$。
Why it matters: 说明 Agent Step Graph 比 CFG / DAG 更通用的原因——把"依赖类型"抽象成 per-node 的 aggregation function,一次性支持 join、branch、synchronization barrier。
Detailed description: 两张并排的小 DAG,节点标注 agent_name (STE),边从 predecessor 指向 successor。最右上方有一个图例说明 aggregation function 的语义:max+1 = 并行依赖需全部完成,min+1 = 择一路径即可。

What it shows: radix tree 里每个 node 的驱逐优先级。Agent 的 STE 先赋给其 fixed prompt 的最后一个 token 对应的 node,然后向树根方向传播;当一个 node 被多 agent 共享时取 min (最保守,最难驱逐)。动态后缀统一赋最高驱逐优先级。
Why it matters: 把 "agent 级的未来信号" 下沉到 "KV node 级",解决多 agent 共享前缀的冲突——共享 prefix 不会被某一个 agent 的驱逐需求误伤。
Detailed description: 一棵 radix tree,根节点到多个 agent 的 fixed-prompt 末端节点构成多条路径。每个 node 旁边标注了其继承的 STE 数字。共享分叉 node 被高亮并标注 min(STE_i);每个 agent 下挂的 dynamic-suffix node 被打上 "always-evict-first" 标签。

What it shows: 三条时间线对比:① reactive loading (baseline, HiCache):当 Executor1 被 schedule 时才开始 load KV,GPU 干等;② proactive prefetching only:在 Planner 执行期间预取 Executor1,但如果 Planner 执行短于 load 时间,Executor1 开始时仍要等;③ KVFlow (proactive + status-aware):Executor1 还在 loading 时调度器直接跳过它去跑 Executor2 或别的 ready 请求,GPU 全程不空转。
Why it matters: 这是 "GPU 利用率从 ~70% 拉到 >95%" 的根本原因——PCIe load 和 GPU compute 不仅要 overlap,还要有"bypass 未 ready 请求"的 scheduler 逻辑。
Detailed description: 三条水平 swim lane,每条分成 GPU-compute、PCIe-load、Scheduler-decision 三个 track。红色区块 = GPU 空转;绿色 = compute;蓝色 = PCIe transfer;黄色箭头 = scheduler 把请求推到 queue 尾部。KVFlow 条完全没有红色区块。
| Setting (Fixed/Dynamic/Output) | Platform | vs SGLang (GPU-only) | vs SGLang+HiCache |
|---|---|---|---|
| 4096 / 32 / 32 | A10G / Llama-3.1-8B | ~1.9× | ~1.2× |
| 8192 / 32 / 32 | A10G / Llama-3.1-8B | 2.91× | 1.83× |
| 4096 / 32 / 32 | H100 / Qwen2.5-32B | ~1.5× | ~1.3× |
| 8192 / 32 / 32 | H100 / Qwen2.5-32B | ~2.0× | ~1.5× (HiCache 在 H100 大场景下反而退化) |
| 8192 / 32 / 512 (长 decode) | 双平台 | ~1.2× | ~1.05× (decode 占比高时收益降) |
Takeaway: 加速倍数 随 fixed prompt 长度单调上升(cache miss 代价占比越大)、随 output 长度单调下降(decode 占比越大 serving 瓶颈越不在 prefix)。
| Fixed tokens / concurrency | vs SGLang | vs SGLang+HiCache |
|---|---|---|
| 512 / 32 | 1.15× | 1.10× |
| 1024 / 32 | 1.25× | 1.20× |
| 1024 / 64 | 1.25× | 2.19× (HiCache 只有 0.57× SGLang) |
| PEER real-world sim | 1.12× | 1.08× |
Takeaway: HiCache 在 "1024 fixed × 64 并发" 下 反向劣化到 SGLang 的 0.57×——因为 reactive load 和 SGLang 原本的 layer-by-layer pipeline 打架,frequent cache miss 触发的 load-back 破坏了调度。KVFlow 通过 proactive + bypass 规避了这个陷阱。
sgl.function 层注入 workflow 元数据;非 SGLang 框架要手工适配。
[User task]
│
▼
[Frontend: sgl.function per agent] ──── 注入 workflow metadata (agent_id, STE map) 到 HTTP ───▶
│
▼
[Backend: Request Router]
│
▼
[Radix Tree Prefix Matcher] ── 找出命中的 KV node 路径
│
├── hit (in GPU) ────────────────────────────▶ [Prefill new tokens only]
├── hit (in CPU) ── trigger async load ─────▶ [Wait/skip via status-aware]
└── miss ────────────────────────────▶ [Full prefill recompute]
│
▼
[Scheduler: Status-Aware] ── skip "loading" nodes; prioritize ready
│
▼
[GPU forward: prefill + decode]
│
▼
[Sampler → CPU output] (GPU→CPU direction)
│
▼
[Eviction policy: STE-priority] ── 后台:按 STE 驱逐; 触发 proactive prefetch 下一批
│
▼
[Return tokens to frontend]
| Stage | Input → Output | Location | Latency | Data format |
|---|---|---|---|---|
| sgl.function 截获 | user call → HTTP + metadata | CPU (frontend) | <1 ms | JSON (STE map, agent_id) |
| Radix match | tokens → node path | GPU-resident tree + CPU | μs | tree pointers |
| Prefill (fresh) | tokens → KV | GPU HBM | ~O(n²) for n tokens | [layers, kv_heads, n, dim] |
| KV load (CPU→GPU) | CPU KV → GPU KV | PCIe | n × layers × 2 × dim × 2B / BW | per-layer block |
| Proactive prefetch | 同上 | PCIe (与 compute 同时) | overlapped | per-layer block |
| Decode | KV → logits | GPU HBM | ~ms/token | [vocab_size] |
| Eviction & status update | radix tree + status vars | CPU + GPU | μs | priority queue |
见上文 Phase 2 约束推导;framework 视角的补充:
3a. Alternative Approaches
3b. 可行性矩阵
| 方案 | 预测未来 | 支持 branch/cycle | 共享前缀一致 | 无需离线训练 | 工程复杂度 |
|---|---|---|---|---|---|
| LRU | ✗ | ✗ | ✗ | ✓ | 低 |
| LFU | ✗ | ✗ | ✗ | ✓ | 低 |
| MRU | ✗ (反向) | ✗ | ✗ | ✓ | 低 |
| InferCept cost model | △ | △ | ✗ | ✗ | 高 |
| Agent 级 STE | ✓ | ✓ | ✗ | ✓ | 中 |
| KVFlow (KV-node 级 STE + min) | ✓ | ✓ | ✓ | ✓ | 中高 |
3c. Assumption Audit
3d. Core Technical Barrier
See Phase 2: "KV-node 级 min 聚合 + 4 态 status 机(in-GPU / in-CPU / loading / offloading)与 radix tree eviction 和 scheduler 的原子集成"。这部分没法通过读论文 3 页就 reproduce;需要深入 SGLang 内部,估计 3–4 周 dev effort。
3e. Design Binding Critique
Radix-tree cache (排除 vLLM paged block)、SGLang 前端、静态可观测 workflow、必须有 CPU 二级缓存——四个强耦合。
| Innovation | Mechanism | Benefit | Cost/Tradeoff |
|---|---|---|---|
| Agent Step Graph abstraction | 每节点带 max+1 / min+1 aggregation function | 统一 DAG/cycle/branch | 需要 frontend 暴露 workflow 元数据 |
| STE-based eviction at KV-node level | 每个 radix tree node 继承所属 agent 的 STE;共享 node 取 min | 精准保留"即将被用"的前缀;共享前缀无冲突 | 每次 step 切换需刷新一遍 priority |
| Proactive KV prefetching | 后台线程根据 Step Graph 预测下一步 agent,CPU→GPU 预取 | 完全重叠 PCIe 延迟与 GPU compute | 分支时可能过度预取(有并发限制 cap) |
| Status-aware scheduler | 4 态 {in-GPU, in-CPU, loading, offloading};跳过 loading 请求 | GPU 零空转;eviction 与 loading 无竞态 | 调度器复杂度上升;长 load 可能饥饿短请求 |
agent_name="Planner" 命名冲突;workflow 间 STE 独立计算,共享 prefix node 取 最保守 min (跨 workflow 也是 min)| Scenario | Workload Pattern | SLO / Goal | Why SGLang/HiCache fails |
|---|---|---|---|
| Interactive dev (notebook, batch=1) | 单 workflow 串行 10 agents, 大 fixed prompt | E2E latency 最低 | LRU 误驱逐 + reactive load 串行 |
| High-concurrency multi-workflow serving | 32–64 并发 × PEER-style 4-agent workflow | 吞吐 + tail latency | HiCache 的 reactive load 在高并发下崩溃(0.57× SGLang) |
| Agentic RAG/tool pipeline | Mixed branch/cycle | TTFT < 500ms | 无感知下一跳,prefetch 无从谈起 |
Primary bottleneck:在 fixed-prompt-heavy 场景下是 memory-bound (prefix KV 装不下 GPU HBM) + PCIe-bound (CPU 二级缓存的传输延迟),KVFlow 针对这两者。
| Metric | Definition | Unit | Direction |
|---|---|---|---|
| E2E latency | workflow 所有 agent 依次完成的时间 | ms | ↓ |
| Speedup vs SGLang | latency ratio | × | ↑ |
| Speedup vs SGLang+HiCache | 对 CPU-backed baseline | × | ↑ |
注:论文未单独报告 TTFT / TPOT / P99 / goodput 等 serving 标准指标;只用 E2E latency 和 speedup,这是一个评测深度不足的地方。
见 Key Tables A / B。核心数据:
Baseline (SGLang GPU-only): memory-bound (KV 装不下) → miss → recompute-bound (prefill)
↓
+HiCache: recompute-bound → PCIe-bound (reactive load 串行)
↓
+KVFlow eviction only: PCIe-bound → 仍 PCIe-bound (但 miss 次数减少)
↓
+KVFlow prefetch: PCIe-bound → 当 compute > load 时 → 回到 compute-bound (理想)
↓
+KVFlow status-aware scheduling: 当 compute < load 时 → 靠 bypass 填 GPU → 几乎无 bubble
剩余瓶颈:作者自己承认的 KV fragmented layout 导致 PCIe 带宽无法打满;以及 decode-heavy 场景下的 auto-regressive decode(与 KVFlow 正交)。
sgl.function 装饰器max_concurrent_prefetch、fixed-prompt boundary (显式 / heuristic 两选一) — 调参量小已在 Summary 最后的 Infrastructure Impact 表覆盖。
| Feature | KVFlow | SGLang | SGLang+HiCache | vLLM | TRT-LLM |
|---|---|---|---|---|---|
| Radix prefix cache | ✓ | ✓ | ✓ | Paged (non-radix) | ✓ |
| CPU 二级缓存 | ✓ | ✗ | ✓ | ✓ (v1) | ✓ |
| Workflow-aware eviction | ✓ | ✗ | ✗ | ✗ | ✗ |
| Proactive prefetch | ✓ | ✗ | ✗ (reactive only) | ✗ | ✗ |
| Status-aware scheduling | ✓ | ✗ | ✗ | ✗ | ✗ |
| Multi-node prefix cache | ✗ | ✗ | ✗ | Dev | ✓ |
sgl.function + 后端打 patch"—— 2–4 周 integration