文章

Attention Sinks 实战:用 StreamingLLM 做长会话持续生成

本文从注意力沉降现象出发,解释 StreamingLLM 如何保留初始 Sink Token 与最近窗口,在不微调模型的前提下降低长会话显存压力,并给出生产落地边界、监控指标与上线检查清单。

Attention Sinks 实战:用 StreamingLLM 做长会话持续生成

背景问题:长会话不是把窗口无限拉大

在线助手、客服机器人、实时摘要、语音同传和长时间 Agent 会话都有一个共同挑战:会话持续时间可能远超模型训练时的上下文长度。如果直接保留所有历史 Token,KV Cache 会随着会话长度持续膨胀,显存压力和调度复杂度双双攀升;如果只保留最近窗口,模型又可能在窗口滚动后突然变得不稳定,出现重复、跑题或退化输出。

传统思路大致分为三类:

策略做法代价
长上下文模型直接扩大上下文窗口预填充和 KV Cache 成本更高
摘要/RAG/外部记忆压缩历史信息引入检索与摘要误差
滑动窗口注意力只看最近一段上下文可能破坏预训练时的注意力分布

StreamingLLM 聚焦第三类问题:当服务需要持续生成,但又不能无限保留历史 KV Cache 时,如何在固定缓存预算下维持模型流畅输出。

核心原理:为什么最前面的 Token 不能随便丢

StreamingLLM 的关键观察是 Attention Sink 现象。许多自回归 Transformer 会对序列最开始的若干 Token 分配异常高的注意力,即使这些 Token 在语义上并不重要。换句话说,模型在 softmax 注意力归一化过程中,似乎会把一部分注意力质量”沉降”到初始位置。

如果只使用普通滑动窗口,把最早的 Token 和对应 KV Cache 直接丢弃,模型不仅失去了一些历史内容,还改变了它习惯的注意力分布。这会导致生成质量突然下降。

StreamingLLM 的做法是同时保留两类 KV:

  1. Sink KV:序列开头少量 Token 的 KV,长期固定保留。
  2. Recent KV:最近一段窗口内的 Token KV,随着会话滚动更新。

这样的效果不是让模型拥有无限记忆,而是在固定缓存预算下保持注意力结构稳定。模型仍然只能直接看到最近窗口和初始 sink 部分;中间被淘汰的历史如果还需要使用,必须通过摘要、检索或业务状态重新注入。

工程落地:把它当成会话缓存策略,而不是 Prompt 技巧

StreamingLLM 更像一种 KV Cache 管理策略,而不是简单的 prompt 模板。落地时建议从会话生命周期设计入手。

会话缓存结构

一个可落地的结构通常包含三层:

SessionState
├── sink_tokens:      会话开头固定保留的 Token
├── recent_window:    最近窗口 Token
├── evicted_history:  已淘汰历史的摘要、索引或业务状态
└── metrics:          延迟、显存、截断、退化检测指标

其中 sink_tokens 不应频繁变化,否则就失去了稳定注意力分布的意义。recent_window 应随生成和新输入滚动。evicted_history 不是模型原生上下文,而是应用层补偿机制:例如对话摘要、结构化任务状态、RAG 文档引用、用户偏好或工具调用结果。

推理链路

在服务端,链路可以设计为:

request_flow:
  - load_session_state
  - append_user_message
  - keep_sink_tokens
  - update_recent_window
  - inject_required_memory_if_needed
  - run_decode_with_bounded_kv_cache
  - update_session_state
  - emit_metrics

注意这里的重点不是”每次把全部历史重新拼进 prompt”,而是让会话有一个持续存在的状态。对于低频会话,可以把状态落到 CPU 或对象存储;对于高频在线会话,可以在 GPU/CPU 层做分级缓存,但需要控制每个租户的缓存预算。

与 RAG、摘要、长上下文模型的关系

StreamingLLM 不能替代 RAG。它保留的是生成稳定性所需的注意力结构和最近上下文,不负责找回很久以前的事实。如果业务需要追问 30 分钟前的细节,系统仍然需要把那段信息通过摘要或检索重新放回最近窗口。

它也不能完全替代长上下文模型。长上下文模型适合一次性阅读长文档、代码仓库或合同;StreamingLLM 更适合持续交互、不断产生新 Token 的场景。两者可以组合:长上下文模型负责读取大段材料,StreamingLLM 风格的缓存策略负责会话持续运行。

适用场景

场景说明
长时间在线助手桌面助手、运维助手、客服助手。用户持续输入,系统持续回答,但真正需要精确引用的通常是最近几轮对话和外部状态。
实时摘要与会议伴随会议或直播内容不断进入系统,模型需要持续生成阶段性摘要。保留最近窗口,同时把已淘汰内容写入结构化摘要。
流式 AgentAgent 长时间运行时,工具调用记录、计划状态和最近观察值比完整自然语言历史更重要。任务状态必须外置。
边缘或单卡部署当显存预算固定,不能为单个会话无限扩容时,保留 sink + recent window 是更可控的策略。

常见误区

误区一:它等于无限上下文

不等于。StreamingLLM 可以让模型在长时间生成中保持流畅,但被窗口淘汰的正文不会自动保留语义可见性。需要长期记忆时,必须配合摘要、检索或数据库。

误区二:只要保留第一个 Token 就够了

不一定。不同模型、不同 tokenizer、不同系统 prompt 和不同训练方式都可能影响 sink 行为。生产环境应把 sink 数量做成可配置项,并通过离线评估确定默认值。

误区三:所有任务都适合滑动窗口

需要全局一致性的任务并不适合。例如整篇合同审查、长代码库修改、长论文细读,丢弃中间内容可能破坏推理链。对于这类任务,应优先使用长上下文、分块检索或结构化分解。

误区四:只看显存节省,不看质量退化

固定 KV 预算会降低显存压力,但也会引入上下文丢失。上线时不能只看 tokens/s、TPOT、显存占用,还要看回答是否遗漏早期约束、是否重复、是否在长时间运行后退化。

上线检查清单

参数配置

上线前至少需要固定以下参数(数值仅为示意,不应直接作为生产默认值):

streaming_cache:
  sink_token_count: 4
  recent_window_tokens: 4096
  max_session_tokens: 200000
  evicted_history_policy: summarize_and_index
  quality_guardrail: enable

实际值要基于模型、业务输入长度、显存预算和质量评估确定。

质量评估

建议构造三类测试集:

测试类型验证目标
持续闲聊测试验证长时间生成是否重复、发散或崩溃
近期事实测试验证最近窗口内的信息是否能稳定引用
被淘汰历史测试验证系统是否能通过摘要或检索找回旧信息,而不是误以为模型原生记得

性能评估指标

需要至少观察以下维度:

  • TTFT:首 Token 延迟
  • TPOT / ITL:单 Token 输出延迟
  • KV Cache 占用:每会话、每租户、每 GPU 的缓存预算
  • 窗口截断次数:是否频繁丢弃关键内容
  • 会话存活时间:长会话是否挤压短会话资源
  • 退化信号:重复率、拒答率、格式错误率、用户重试率

回退策略

生产系统应支持三种回退:

  1. 关闭 StreamingLLM 策略,回到普通上下文窗口。
  2. 缩短会话生命周期,强制摘要并重建新会话。
  3. 对高价值任务切换到长上下文模型或离线批处理。

架构组合:职责分离才是关键

比较稳妥的生产架构不是单独依赖 StreamingLLM,而是把它放在缓存层:

用户请求

会话状态管理

短期窗口:sink tokens + recent tokens

长期记忆:摘要 / RAG / 数据库 / 工具状态

推理服务:bounded KV cache decode

质量监控与回写

这套结构的核心是职责分离:StreamingLLM 管生成稳定性,RAG 管事实找回,摘要管历史压缩,状态机管任务进度,监控管退化发现。只要把职责混在一起,就容易误判效果:显存降了,但任务正确性可能下降。

FAQ

StreamingLLM 是否需要微调模型?

原始 StreamingLLM 目标是在不微调的情况下,让有限上下文训练的模型适配持续生成。论文也讨论了在预训练中加入专门 sink token 可能进一步改善流式部署,但生产采用时仍要以实际模型评估为准。

被窗口淘汰的历史还能被模型使用吗?

不能直接使用。被淘汰的 Token 不在当前注意力可见范围内,模型无法像完整上下文那样访问。需要使用摘要、RAG 或结构化状态重新注入。

它和 KV Cache Offloading 有什么区别?

KV Cache Offloading 是把缓存从 GPU 移到 CPU、磁盘或远端存储,目标是扩大容量或降低 GPU 显存压力;StreamingLLM 是改变保留哪些 KV 的策略,目标是在固定窗口下保持生成稳定。两者可以组合,但优化目标不同。

参考资料

  1. Efficient Streaming Language Models with Attention Sinks: arXiv:2309.17453
  2. StreamingLLM GitHub: mit-han-lab/streaming-llm
  3. Hugging Face: Attention Sinks in LLMs for endless fluency: Blog
  4. Hugging Face Transformers KV Cache Strategies: Docs
  5. FlashInfer Attention Kernels: Docs
  6. FlashInfer: Efficient and Customizable Attention Engine for LLM Inference Serving: arXiv:2501.01005
  7. When Attention Sink Emerges in Language Models: An Empirical View: arXiv:2410.10781
  8. Attention Once Is All You Need: Efficient Streaming Inference with Stateful Transformers: arXiv:2605.13784

常见问题

StreamingLLM 能替代长上下文模型吗?
不能。它主要解决持续生成时的稳定性与显存增长问题,只保留初始 Sink Token 和最近窗口,不会让模型完整记住被淘汰的历史内容。
Attention Sink Token 应该保留多少个?
需要通过模型和业务数据压测确定。工程上应把 sink 数量、最近窗口大小、最大会话时长作为可配置参数,并用质量评估与延迟指标共同验证。
它适合 RAG 或 Agent 场景吗?
适合处理持续会话的生成稳定性,但不应替代检索、摘要和任务状态管理。RAG 与 Agent 仍需要外部记忆、状态机和工具调用记录。