一个没人回复的 Issue
7 月 6 日,一位 GitHub 用户在开源推理引擎 SGLang 的仓库里提交了一份缺陷报告,把问题写得相当完整:在预填充和解码分离部署(PD 分离)的模式下,超长请求会被内部中止(abort),提示词被换成 1 个 token 的占位符,但这个请求仍然进入预填充队列,只把 1 个 token 的 KV 缓存传给解码端;解码端按完整提示词长度分配了显存,于是“从不完整、未初始化的 KV 缓存开始解码,输出乱码”。报告里附了根因分析和一段修复补丁。
这份 #30233 底下只有一条回复,来自 github-actions 机器人。9 月 5 日,机器人以“长期无活动”为由自动关闭了它。
三天后,安全公司 Knostic 的 CEO Gadi Evron 通过 HackerOne 向 Cloudflare 报告:在 Cloudflare Workers AI 上给 GLM-5.3 模型发送很长的提示词,有时会收到属于别的用户会话的文字。Cloudflare 10 月 26 日公布的事故报告确认,8 月 27 日到 9 月 9 日之间共有 155 条响应受影响,全部是长度超过 681,280 token 的提示词,内容来自其他客户不久前发给同一模型的请求。根因之一,正是 #30233 描述的那条代码路径。Cloudflare 在报告里写明,这个缺陷“此前已由另一位用户作为 issue #30233 报告给 SGLang”,是它在调查时才注意到的。
这起事故的价值不在 155 这个数字,而在它暴露的分工。开源推理引擎已经是云厂商多租户服务的底座,但“不同客户的数据不能串”这件事,并没有明确落在哪一方:上游项目按功能和性能推进,问题单可以被机器人关掉;云厂商按引擎的行为做假设,假设错了也没有报错提醒。对自建推理或挑选推理服务商的小团队来说,这条缝隙同样存在,而且更难看见。
两个各自合理的假设
按 Cloudflare 的说法,事故由两个问题叠加造成。第一个是它自己的配置:GLM-5.3 对外提供 1,048,576 token 的上下文窗口,但预填充 GPU 上的 KV 缓存池只装得下 681,280 token。Cloudflare 写道,上线前就知道这个差距,“我们以为引擎会对处理不了的提示词返回错误”,而长上下文测试返回的都是成功状态码,“所以我们不认为这是个问题”。
第二个是 SGLang 的缺陷。单机部署时,被拒绝的请求会正常返回错误;PD 分离模式下,预填充和解码两端的准入队列只检查请求是否“已结束”,不检查是否“被拒绝”,被拒绝的请求于是一路走完交接。解码端收到“传输完成”的信号,就在大部分没被覆盖的旧缓存页上生成回复。客户端拿到的是 HTTP 200,prompt_tokens 与长提示词一致,回复本身也通顺。
两个假设单看都说得通:云厂商相信引擎会拒绝装不下的请求,引擎在单机模式下也确实会拒绝。问题出在两者交界的那种部署形态上。Cloudflare 在报告中解释,KV 缓存池的大小不是配置项,而是 SGLang 启动时按加载权重后剩余的显存算出来的,对外宣称的上下文长度则是另一个配置值,“没有任何机制强制两者一致,检查它们是否一致是我们的责任”。在给其他运营方的建议里,它把这一条列为第一项,并写道:“这正是我们没有执行的那项检查。”
外部用户看到的则是另一层信息。Knostic 10 月 27 日发布的报告显示,异常最早出现在 Evron 通过 OpenRouter 做的日常工作中:输出里出现了不属于他机器的文件路径和中文教程。9 月 7 日排查确认服务商是 Cloudflare 后,他改用自己的 Workers AI 账号测试。一个 662,162 token 的请求耗时 68.9 秒,正确回显了标记;一个 812,377 token 的请求只用了 5.0 秒,标记消失,内容与提示词无关,却按完整长度计费,共 103,604.64 neurons。Knostic 还写道,当时 Cloudflare 文档标注的上下文窗口是 1,310,720 token,Cloudflare 事后向它说明这个数值是文档错误,实际限制为 1,048,576 token。
补丁、版本和“没法判断”
修复进展可以从公开记录里逐条核对。Cloudflare 称能阻止这次泄露的修复 #36988(原本针对一条相关的失败路径)在 9 月 6 日合入主干,比事故报告早两天,但当时还没发版;GitHub 上的版本比对显示,它最早进入 9 月 18 日发布的 v0.5.20。更早一步拦截被拒请求的 #38935 于 9 月 16 日合入,进入 10 月 2 日发布的 v0.5.21。Cloudflare 建议 PD 分离部署的运营方升级到 10 月 19 日发布的 v0.5.22 或更高版本;SGLang 的建议实际也落在 v0.5.22,因为 MORI 后端的修复和释放指标都在这一版。
SGLang 团队在 LMSYS 博客上发布了配套说明,讨论范围比 Cloudflare 这一起事故更宽。它描述了同一类缺陷中的另一种:请求在 KV 缓存传输中途被中止时,已经发出的 RDMA 写操作无法撤回,如果解码端立刻把这些显存页分配给别的请求,迟到的写入就会落进别人的缓存。按 SGLang 的说法,在 PD 部署中,从 2025 年初的 v0.4.x 起,默认配置就受这一竞态影响,直到 v0.5.21 才在默认配置下修复(MORI 后端要到 v0.5.22),相关修复没有回移到更早的发布分支。对“部署方能否判断过去是否已经发生过泄露”,它的回答是:“没有可靠的办法,因为这种损坏不会留下任何错误或日志。”
防线也还不完整。新增的 KV 传输校验 --disaggregation-enable-kv-checksum 默认关闭,因为每次传输都要多读一遍显存。Cloudflare 写道,校验和 --kv-canary 目前还不支持所有模型架构,GLM-5.3、GLM-5.3 Flash 和 DeepSeek V4 都在待扩展之列。显存页在请求之间不清零,Cloudflare 称这种做法在推理引擎中很常见,是拿到最佳性能所必需的;它提交的“释放时清零 KV 页”补丁 #46435 截至 10 月 28 日仍是草案。
另一种读法
也有理由认为,这次是机制正常运转的例子。Cloudflare 的时间线显示,它在 9 月 9 日宣布事故后 40 分钟内下线了模型,同一天恢复服务;它的报告列出了 20 个已合入 SGLang 主干、针对这起事故和相关竞态的 PR,SGLang 维护者所在的公司 RadixArk 参与了修复。Cloudflare 还写道,它“从 v0.5.10 起就一直在向 SGLang 反映这类问题”,这次事故加快了它此前已与 SGLang 团队合作开展的工作。它称排除测试流量后,受影响请求的时间和数量与普通长上下文使用一致,没有发现蓄意利用的证据。Evron 在报告里称赞了 Cloudflare 处理漏洞报告的方式。SGLang 在博客末尾写道,维护者会优先处理这一类报告。代码开源,也让外部可以逐条核对每个补丁合在哪天、进了哪个版本。
性能上的取舍也是真实的。显存页不清零,Cloudflare 称是为了拿到最佳性能;校验默认关闭,SGLang 称是因为每次传输都要多一次显存读取和同步。PD 模式本身也还在被继续调快,v0.5.22 的发布说明称,其中一项改动在长提示词负载上让吞吐最多提高 27%。安全检查每多开一项,都要和这些数字放在一起权衡。
但正因为如此,“谁来检查”就更重要。此前一篇关于 DeepSeek 开源推理引擎的长文提出,引擎代码打平之后,竞争会转向运维这些没开源的部分。这次事故说明,租户隔离也落在这一层:Cloudflare 在报告里提出的长期方案,包括给每个 KV 页加标签、在读取时校验归属,以及对 RDMA 传输协议做形式化验证。它 8 月公开的原型开销不到 1%,但因为可靠性问题已经停用,目前正与 SGLang 合作做跨模型的通用版本。
对小团队而言,这件事至少给出两点可以核对的事实。自己用 SGLang 做 PD 分离部署的,需要的检查清单已经写在两家的公开报告里:KV 池大小对比上下文长度、版本不低于 v0.5.22、按需开启校验并观察释放指标;SGLang 称单机部署不受这类跨引擎传输竞态影响。通过 OpenRouter 这类路由服务调用开源模型的,Knostic 是先排查才确认背后的服务商是 Cloudflare;服务商用的是哪个引擎、哪个版本,要看服务商自己是否公开。Knostic 的检测办法并不复杂:在提示词首尾放唯一标记,逐步加长,看标记是否还能回显。
开头那份缺陷报告(#30233)被机器人关闭的那天,GLM-5.3 已经在 Workers AI 上运行了 9 天。那份报告给出的修复思路,是让被中止的预填充请求不再传输 KV 缓存,并通知解码端一起中止。两个月后合入的补丁,走的是同一个方向。
