@_avichawla: 代理强化学习(Agentic RL)的一个有趣新方法:研究人员找到了让 AI 代理训练速度提升近 2 倍的方式。代理强化学习通常……
摘要
研究人员提出了 KV-streams,一种通过直接修剪运行中的 KV 缓存而非重新预填充来压缩代理 RL rollout 的方法,在 SWE-bench Verified 上几乎将 RL 训练速度提升一倍,同时使训练与生成的 token 分布几乎完全一致。
查看缓存全文
缓存时间: 2026/10/03 06:53
一种有趣的智能体 RL 新方法:
研究人员找到了一种让 AI 智能体训练速度提升近 2 倍的方式。
Agentic RL(智能体强化学习)通常会产生很长的 rollout,当 rollout 超出上下文预算时,压缩策略(compaction policy)会移除较早的轮次,保留 prompt、最近的轮次或摘要。
大多数系统使用 re-prefill(重预填充)压缩。
它们先构造一个更短的序列,然后让所有保留的 token 再次经过模型。这会在每次压缩后重建 KV cache,因此相同的 token 可能被反复处理。
KV-streams 是解决这一问题的新方法。
它不重建较短的序列,而是直接从活跃的 KV cache 中删除被丢弃的轮次。生成操作从存活下来的条目处继续进行。
在实践中,这些条目并不能原样复用,需要两处修正。
首先,RoPE 已经使用原始 token 位置旋转了每个缓存的 Key。压缩后某个 Key 可能从物理槽位 1,000 移到槽位 500,但模型仍必须将其视为位置 1,000。
KV-streams 将物理存储与逻辑位置分离。保留下来的 Key 保持原有的旋转,新的 Query 和 Key 则从原始位置计数继续。
其次,vLLM 并不独立存储每条 KV 条目,而是将其组织成固定的 16-token 块,并用块表(block table)在 GPU 内存中定位这些块。
KV-streams 不能在该表中留下空槽位,否则注意力内核可能读取到错误的内存。因此它按完整块进行删除,并将剩余的块引用拼接在一起。
这就引出了另一个问题。
对话轮次可能在一个 16-token 块的中途结束。在他们的 vLLM 设置中,这个不完整的块不会被提交到前缀缓存(prefix cache),下一轮到来时必须重新计算。
如果压缩先发生,这些 token 最初所注意到的上下文可能已经不存在了。
对于 vLLM 实验,研究人员将每个补全填充到 16 token 的整数倍。这样在下一轮或压缩事件发生之前,每个生成的 token 都已提交到缓存中。SGLang 设置使用单 token 块,无需填充。
此外,研究人员还发现,token 级别的淘汰会产生退化的文本,并需要大量的监督微调。
他们怀疑,删除消息边界 token 破坏了训练中学习到的聊天格式。因此智能体实验删除的是完整轮次,包括其边界标签。
一旦推理按这种方式工作,训练也必须遵循同样的流程。
常规训练器要么处理完整的 rollout,要么在压缩后重建较短的序列。这两种方式产生的 KV 条目都与生成阶段使用的不同。
KV-streams 会记录生成过程中发生的每一次压缩事件。训练器随后复现这段历史:保留的 token 维持压缩前形成的状态,而后续 token 则被阻止注意到推理阶段已经移除的片段。
在仓库的验证运行中,训练与生成之间的差异始终保持在约 0.001 的失配 KL(mismatch KL),表明两条路径产生了几乎完全相同的 token 概率。
在 SWE-bench Verified 上,使用 KV-streams 的 Markovian Thinker 设置在约 32 小时内达到了峰值性能。
采用相同压缩策略但使用 re-prefill 的方法大约需要 60 小时,而两者的最终得分相当。
这种加速主要适用于 RL 训练,因为一次 rollout 可能被多次压缩。
在生产推理阶段,批处理可以掩盖部分预填充成本,而为不活跃用户保留缓存则会降低服务容量。
还有一个细节值得注意。
被保留的 KV 条目是在被删除的轮次仍然可见时创建的。后续 token 无法直接注意到这些轮次,但它们的部分影响可能仍残留在未被淘汰的条目中。
在一项受控测试中,RL 智能体在原始消息被删除后仍恢复了一个指派。当保留的缓存足够多时,召回率达到了 100%。
KV-streams 并不决定模型应该忘记什么,而是通过保留压缩策略所选择的 KV 状态,让现有的压缩策略训练成本更低。
仓库:https://github.com/Emilianopp/KV-streams…
如果你想深入了解更广泛的 KV-cache 工程技术栈,我写了一篇文章,涵盖了淘汰(eviction)、分页(paging)、量化(quantization)、卸载(offloading)以及架构级压缩等内容。
请在下方阅读。
Emilianopp/KV-streams
来源:https://github.com/Emilianopp/KV-streams
KV-streams
面向 RL 训练的原生 vLLM KV cache 压缩。这是 vLLM 0.19 的一个 fork,集成了调度器级的块级淘汰(block-level eviction)。
当某个请求的 KV 长度超过 --compaction-window-size 时,调度器会淘汰最旧的 prompt 之后的块(每次淘汰 --compaction-stride)。该请求的物理长度变短——注意力计算更快,无需重新计算 KV。
环境搭建
前置要求
- 配备 A100(或更新型号)GPU 的 CUDA 12.8 环境
- Python 3.12
uv(https://docs.astral.astral.io/uv/)包管理器- 建议但不强制使用容器运行时。一个合适的基础镜像是
docker.io/novaskyai/skyrl-train-ray-2.51.1-py3.12-cu12.8,其中捆绑了 CUDA 12.8、Python 3.12 和uv。
1. 克隆仓库
bash git clone --recursive https://github.com/Emilianopp/KV-streams.git cd KV-streams
vllm/(kv-streams-vllm (https://github.com/Emilianopp/kv-streams-vllm))和
prime-rl/(kv-streams-prime-rl (https://github.com/Emilianopp/kv-streams-prime-rl))是子模块。如果你克隆时没有加 --recursive:
bash git submodule update --init
2. 安装
``bash
一键安装(创建虚拟环境、以可编辑模式安装 vLLM + 全部依赖)
bash setup.sh ``
或者手动安装(适用于开发场景):
``bash uv venv .venv –python python3.12 –clear source .venv/bin/activate
以可编辑模式安装启用了压缩功能的 vLLM fork。VLLM_USE_PRECOMPILED=1
会下载 vLLM CI 构建的 .so 产物并将其符号链接到源码树中,
这样对 vllm/vllm/v1/core/compaction/ 下 Python 代码的修改
无需重新完整编译 C++/CUDA 即可生效。
VLLM_USE_PRECOMPILED=1 uv pip install -e ./vllm –no-build-isolation
kv-eviction 集成层
uv pip install -e . ``
3. 验证安装
bash source .venv/bin/activate python -c " from vllm.v1.core.compaction import CompactingKVCacheManager, CompactionEvent print('Compaction imports OK') import vllm; print(f'vLLM {vllm.__version__}') "
推理
快速开始——启用压缩的 vLLM server
``bash source .venv/bin/activate
启动启用了压缩功能的 vLLM。
window=4096:KV 超过 4096 个 token 时触发淘汰
stride=512:每次压缩事件淘汰 512 个 token(32 个块)
block_size 默认为 16,stride 必须是它的整数倍
async_scheduling=False 是必需的(见下方“约束条件“)
python -m vllm.entrypoints.openai.api_server
–model Qwen/Qwen3-4B
–max-model-len 16384
–enable-prefix-caching false
–async-scheduling false
–compaction-window-size 4096
–compaction-stride 512
–tensor-parallel-size 1
–port 8000
``
查询服务
``bash
标准的 OpenAI 兼容 API——压缩过程完全透明
curl -s http://localhost:8000/v1/chat/completions
-H “Content-Type: application/json”
-d ‘{
“model”: “Qwen/Qwen3-4B”,
“messages”: [{“role”: “user”, “content”: “请分步求解:1234 * 5678 等于多少?”}],
“max_tokens”: 8192,
“temperature”: 0.7
}’ | python -m json.tool
``
Python 客户端
``python from openai import OpenAI
client = OpenAI(base_url=“http://localhost:8000/v1”, api_key=“unused”)
response = client.chat.completions.create( model=“Qwen/Qwen3-4B”, messages=[{“role”: “user”, “content”: “写一篇关于 AI 安全性的长文。”}], max_tokens=8192, temperature=0.7, ) print(response.choices[0].message.content) ``
离线推理(无需 server)
``python from vllm import LLM, SamplingParams
llm = LLM( model=“Qwen/Qwen3-4B”, max_model_len=16384, enable_prefix_caching=False, async_scheduling=False, # 使用压缩时必需(见“约束条件“) # 压缩相关参数 compaction_window_size=4096, compaction_stride=512, )
outputs = llm.generate( [“请详细介绍量子计算。”], SamplingParams(max_tokens=8192, temperature=0.7), ) print(outputs[0].outputs[0].text) ``
工作原理
`` 请求正在生成 token… [prompt: 200 个 token] [gen: 3800 个 token] KV 总量 = 4000(低于窗口)
生成下一个 token 后: [prompt: 200 个 token] [gen: 3801 个 token] KV 总量 = 4001(仍然低于窗口)
…又过了 96 个 token: [prompt: 200 个 token] [gen: 3897 个 token] KV 总量 = 4097(超过窗口!)
压缩触发:
- 从 KV cache 中淘汰最旧的 512 个生成 token(32 个块)
- 相应地裁剪 token ID
- 将 num_computed_tokens 减少 512
- 添加 position_offset += 512 以确保 RoPE 正确
- 标记该请求,让模型执行器重建
压缩之后: [prompt: 200 个 token] [gen: 3385 个 token] KV 总量 = 3585(回到窗口以内) 物理 seq_len = 3585(注意力计算更快!) RoPE 位置 = 物理位置 + 512(绝对位置仍然正确) ``
该请求会像什么都没发生过一样继续生成。注意力开销由窗口大小界定,而不再随生成长度线性增长。
配置
| 参数 | 默认值 | 说明 |
|---|---|---|
--compaction-window-size | 0(关闭) | 触发淘汰的 KV token 数量 |
--compaction-stride | 0 | 每次事件淘汰的 token 数(必须是 block_size 的整数倍) |
约束条件
- 必须设置
--enable-prefix-caching false(压缩会对块进行拼接) - 必须设置
--async-scheduling false。在异步模式下,vLLM 会在处理当前步输出之前就预先调度下一步,因此当update_from_output执行时,num_output_placeholders始终不为零。而压缩触发条件被限定为num_output_placeholders == 0,因此在异步调度下压缩永远不会触发,运行会悄无声息地退化为全上下文模式。调度器在初始化时会对此进行断言检查。 - 不支持流水线并行(
--pipeline-parallel-size > 1) stride必须是block_size(默认 16)的整数倍window_size必须大于stride
推荐设置
| 使用场景 | 窗口大小 | 步长 | 说明 |
|---|---|---|---|
| 长文本生成 | 4096 | 512 | 速度与上下文的良好平衡 |
| 超长文本生成 | 8192 | 1024 | 保留更多上下文 |
| 激进压缩 | 2048 | 512 | 极致速度,上下文较少 |
RL 训练(TextWorld)
configs/textworld/ 目录保存了论文中 TextWorld 实验结果背后的配置:在单节点 8 卡 H100(4 张用于推理 + 4 张用于训练)上运行 Qwen3-4B-Instruct-2507,batch 大小 512,每个 prompt 生成 8 条 rollout,共 500 步,预算 32k,每 10 个轮次压缩一次。每个文件都是独立完整的,并已对照其产生的运行的 W&B 配置进行过核验。
| 策略 | KV-streams | Re-prefill 压缩 |
|---|---|---|
| 全上下文(参照组) | full_context.toml | — |
| 滑动窗口 | kv_streams_sliding_window.toml | reprefill_sliding_window.toml |
| Markovian Thinker | kv_streams_markovian_thinker.toml | reprefill_markovian_thinker.toml |
| 摘要 | kv_streams_summary.toml | reprefill_summary.toml |
sft/ 目录保存了 SFT 预热 + RL 运行(论文 6.1 节)的配置:与上面相同的 KV-streams 滑动窗口和 Markovian Thinker 配置,从 ppEmiliano/qwen3-4b-textworld-sft-eviction-mixed (https://huggingface.co/ppEmiliano/qwen3-4b-textworld-sft-eviction-mixed) 初始化。
``bash
构建数据集(见 reproducing.md),然后替换其路径:
sed “s|TEXTWORLD_DATASET|$PWD/data/textworld_all4_total60000_train59744_eval256_seed20260719|g”
configs/textworld/kv_streams_sliding_window.toml > /tmp/run.toml
uv run rl @ /tmp/run.toml
``
KV-streams 实验组设置 kv_mode = "kv-eviction";re-prefill 实验组则设置 markovian_thinker.enabled = true 并关闭 kv_eviction。添加 --dry-run 可在不启动运行的情况下进行校验并导出解析后的配置。
训练过程中需要关注的指标
progress/reward/mean—— 应呈上升趋势。loss/mismatch_kl_mean—— 训练器与推理的 logprob 一致性。当训练和推理看到相同的 KV 时,该值处于内核精度下限(约 1e-3);任何上升都意味着正确性 bug。progress/entropy/mean—— 保持平稳或轻微下降属正常;熵值塌缩则表明发生了模式塌缩(mode collapse)。
项目结构
KV-streams/ vllm/ # 子模块:kv-streams-vllm(vLLM 0.19 fork,含 KV 淘汰) vllm/v1/core/compaction/ prime-rl/ # 子模块:kv-streams-prime-rl(训练器,淘汰机制接入代码) src/kv_eviction/ # 集成层(环境补丁、填充、分段前向传播) configs/textworld/ # 论文 RL 配置,每个实验组一个文件 plans/ # 代码注释中引用的设计笔记 experiments/ textworld_env/ # TextWorld 环境包、数据集生成、评估 scripts/ # 分析 / 基准测试工具 tests/ setup.sh # 一键安装
相似文章
@dair_ai:NVIDIA 最新研究。他们刚刚发布了一个面向智能体强化学习的 PyTorch 原生训练框架。(收藏)论文摘…
NVIDIA 发布了 Molt,一个面向智能体强化学习、注重紧凑性和可读性的 PyTorch 原生框架,性能与基于 Megatron 的堆栈相当。该框架是开源的,并附有论文。
使用 Prime-RL 后训练构建快速准确的智能体(22 分钟阅读)
Ramp 介绍了一项案例研究,利用强化学习后训练构建了 Fast Ask,这是一种专门的电子表格检索智能体,与通用模型相比,它提高了准确性并降低了延迟。
DualKV: 针对大规模生成和长上下文的共享提示Flash Attention,用于高效RL训练
介绍DualKV,一种FlashAttention内核变体,可消除RL后训练(GRPO/DAPO)中冗余的提示词元计算,在30B MoE模型上实现高达3.82倍的加速。
@dair_ai: 来自 Google DeepMind 及其合作者的新工作。代理算法发现在具有快速奖励的问题上不断得到验证…
Google DeepMind 的 ArchAgent v2 使用代理算法发现自动化了多层次数据预取器设计,在数据预取竞赛中超越了手工设计的解决方案。
@askalphaxiv: “FlashREINFORCE:无批评器单次采样异步强化学习用于智能语言模型” 本文认为可扩展的智能体强化学习…
FlashREINFORCE 引入了一种无批评器、单次采样的异步强化学习框架,用于智能语言模型,通过立即从每个轨迹学习来提高稳定性和效率。