推理与性能AI 产品与平台产业与竞争

对话状态该放在谁那里:SGLang 把赌注押在了推理服务

在过去的两年里,在语言模型接口上构建各种应用或者agent,基本上都不用担心对话状态应该放在什么地方。当时最通用的做法是发送一个无状态的请求,在应用端带上整个对话历史,在服务端进行计算之后就把状态丢弃。在这种情况下,如果我们要更换供应商,只需要更换接口地址,而负载均衡也非常简单。但现在情况发生了变化,因为很多agent需要进行耗时的后台任务,而语音对话又需要支持随时被打断,所以越来越多的状态开始放在服务端。在这种情况下,发起一个请求非常容易,但中途取消却要困难得多。例如,用户在半路上改变了主意,之前发起的后台任务仍在执行,如果我们的应用没有阻止它并且丢弃之前的结果,那么稍后返回的输出就会混进新一轮对话里被播报出来。

OpenAI在2025年3月推出了面向智能体的Responses API,同时仍然支持Chat Completions。根据OpenAI对话状态文档的介绍,新接口的回复默认保存在服务端,可以跨轮次复用。对话历史一旦不再随每次请求携带,状态由谁托管就决定了未来更换引擎的成本。如果你自己部署模型或对外提供推理服务,这直接牵涉到推理引擎这一层:引擎要不要托管会话状态。就算你只调API,供应商背后的推理引擎也在做同样的选择,它决定了你拿到的会话接口长什么样、换供应商要迁移什么。实时语音是最先把这个问题逼出来的地方。今年10月,开源推理引擎SGLang给出了一份答卷。我们站在设计者的位置,看看它面对什么问题、做了哪些决定。

挑战:推理引擎第一次要记住一整段对话

文字聊天是一句一句地发,一句一句地等。实时语音是随时可能被中断。一旦用户中途插话,服务器算出来的东西和用户听到的东西就不一致了,每次请求自带完整历史的无状态模式在这里最先失效。

举个例子,你戴着耳机和手机里的语音助手对话,它准备说三句话,说到第二句中间你突然插嘴问另一个问题。那半截话就同时处于三个地方:远端的显卡上模型已经算到第三句了,服务器刚刚把第二句的音频包发出去,但你的耳机里实际上只听到了第一句和几个字。你一旦开口打断,系统就得立刻停下模型计算、拦住还在网上传输的音频,并且把对话历史裁剪到用户真正听到过的位置。下一轮对话继续进行,助手只该记住用户听到的那半句话。正如 SGLang 官方实时运行时说明指出的那样,服务器生成或者发出了音频不代表这段音频已经被播放出来了,真实的播放进度只能靠客户端反馈。

处理打断,系统需要维护两种状态:一种是在推理引擎内部的状态,记录模型计算到了哪一步,是否可以立即停止;另一种是在客户端的状态,记录耳机实际播放到了哪一步。无论会话由谁管理,都必须有人确保这两种状态保持一致。SGLang 的对齐实现正是为此设计的。

推理引擎知道生成算到了哪里,播放端知道用户听到了哪里,应用里负责串起识别、生成、播放的编排逻辑夹在中间,两头的第一手状态都不在它手里

从推理引擎的设计者的角度来看,另一个挑战是实时语音目前还没有一种统一的方法,市场上有四种方法:第一种是使用三个独立的组件,即语音识别、语言模型和语音合成,将它们进行级联;第二种方法是使用单模型的轮次式,将用户说的一段话作为上下文提供给多模态模型;第三种方法是使用原生全双工,一个模型在连续的音频流中自己决定何时倾听、何时说话;第四种方法是使用原生前台加上委派,一个语音模型在前台自己判断何时倾听、何时说话,和用户实时对话,同时后台的大模型和工具进行耗时的推理和工具调用。

这四种做法把对话状态放在不同的位置。级联的对话状态主要保存在外部的编排程序里,也就是应用里负责串起识别、生成、播放的那层逻辑,状态本身是一份文本历史。轮次式的对话状态保存在单个模型的会话里,由外部判断一句话有没有说完。原生全双工的对话状态保存在显卡上模型持续推进的会话里。原生前台加委派的对话状态横跨前台的语音会话和后台的任务两条快慢不同的路径。四种做法的详细比较可以参考我们的另一篇关于实时语音架构的文章。每种做法都有各自的缺点,第四种做法所需的能力在 SGLang 里还没做完,SGLang 官方只评测了前三种做法,结论是没有哪种做法在所有场景都最好。

在应用层面,开源框架其实已经在做播放对齐了。LiveKit 和 Pipecat 是两个常用的开源实时语音应用框架,它们平时就做轮次管理、打断处理和媒体传输。LiveKit 按播放位置修剪历史的代码会在播放结束后,只把实际同步播出的文字写进对话上下文。Pipecat 播放反馈的代码同样利用时间戳,只在音频真正播出后才把对应文本追加进对话历史。所以按播放位置裁剪历史并不是 SGLang 的首创。SGLang 设计者真正的困境是,面对这四种并存的架构,管住一整段对话这件事该继续留在应用层,还是收进推理引擎。

设计者要什么,于是做了两个决定

是否把对话状态放进推理引擎取决于设计者的具体需求。SGLang 的设计者希望这个系统能同时实现三个目标。第一是上层接口不依赖底层模型和架构,这样开发者在更换模型时不需要重写与上层的交互逻辑。第二是用户打断对话,取消的信号必须能够真正到达计算发生的地点。第三是几种不同的实时语音架构可以在同一个标准下直接进行比较。

在这些目标的驱动下,SGLang的设计者做了两个设计决策,背后有个基本判断是:交互规则的迭代速度比模型架构慢很多。第一个决策是把交互规则固定下来,作为对上层暴露的接口,让下层的模型和组件可以替换。一轮对话怎么定义、打断后取消哪些内容、对话历史按用户听到的位置怎么截断,对上层来说是固定规则。在这个接口下面,开发者可以升级级联里的语言模型而不动语音识别和语音合成,也可以只换轮次检测而不动其他组件,周围的会话基础设施都能直接复用。

第二个决定是把推理引擎的基本单位从单次请求改成持续会话。一个会话从头到尾贯穿整段对话,单次回复结束不会关闭会话,中途取消手头正在进行的工作,也不会弄丢下一轮对话还要继续使用的上下文。

做出第二个决定,设计者就需要牺牲无状态请求的很多好处。无状态的请求彼此独立,不同的推理引擎可以互相替换,请求的分发和负载平衡非常简单,一台机器挂掉,可以把请求转到另一个实例重试。一旦会话状态常驻在推理引擎里,这些好处都需要让渡,应用如果要更换底层的推理引擎,迁移的成本也会高很多。此外,SGLang 目前还没有实现断线恢复,即网络断开重连后接续原来的会话进行聊天。一旦连接中断,会话状态在服务端就无法再接上。

这套设计从年初就开始规划,先有1 月份的设计提案,到 10 月 20 日发布SGLang 的统一交互运行时。这个运行时统一的是交互控制和评测,并没有把不同的架构变成同一种底层计算。

把会话放进推理引擎,在每种做法上站不站得住

第一个决策对所有四种方法都适用,因为无论哪种方法都需要定义怎样算一轮、打断时取消什么、用户听到哪里了。这里我们分析第二个决策,探讨一下对话是否需要放进推理引擎。你用哪种做法决定了你需要不需要一个能保管会话的推理引擎。

先看原生全双工。比如有的模型每 80 毫秒处理一帧音频,对话状态一直在显卡显存里,外部应用看不到。要想让模型停止说话,取消操作必须在推理引擎内部生效。通过SGLang 的控制实现,停止信号可以直接送给引擎正在跑的这次计算。采用这种做法,几乎必须把会话放进推理引擎。

单模型的轮次式是一半在引擎里一半在引擎外。阿里 Qwen 团队的多模态模型 Qwen3-Omni 在 SGLang 里就走这种模式。让模型立刻停止生成必须进入引擎内部,但判断用户一句话有没有说完、实际听到了哪里,放在外部框架里也能做。

级联把对话放进推理引擎的主要原因是为了方便统一四种做法的接口和评测。判断用户听到哪只需要文本、音频和客户端的截断信号,不需要推理引擎的内部信息,也可以在应用层实现。SGLang的初始论文提出了用RadixAttention做前缀缓存,重复的历史算一次就缓存起来,每轮重发历史并不贵,这削弱了级联状态常驻服务端的理由。此外,级联中的识别、生成、合成往往是各自独立的服务。在SGLang的级联组件实现中,SGLang这边取消请求,用户耳边先安静下来,但远端的模型服务未必马上停算,何时停算取决于它怎么处理断连和中止。取消信号到不了计算发生的地方,第二个目标在这里就保证不了,因此把会话放进推理引擎在级联上主要是方便。

最后,原生前台加委派的混合架构需要推理引擎配合。快慢两路共用一个会话,后台计算如果也跑在引擎里,只有引擎知道它现在走到了哪里。如果这种混合架构成为主流,把会话控制放进推理引擎的理由最充分。

混合架构尤其需要引擎与外部编排保持状态同步,例如被打断就丢弃旧结果,迟到的结果不念出来。这些目前各家都还没有做完。SGLang 的异步任务能力仍在它的实时会话路线图上。另一个开源引擎 vLLM 的全模态项目 vLLM-Omni 在推理引擎层走得更前一步(见它的实时协议文档)。OpenAI 的 GPT-Live 则把这件事留给应用处理(见GPT-Live 官方入门文档)。

为什么走这条路,别人给了什么答案

总的来说,原生全双工几乎必然要把会话放在推理引擎里,混合架构如果是主流的话理由最充分但还没有做完,级联放进引擎主要是方便。比拼速度、比拼谁先支持新模型是推理引擎之间常见的竞争,SGLang 在这两条路上并没有明显的优势,因此它在会话这一层做了押注。

SGLang 往上做会话管理,大概有三个原因。第一是学术出身,SGLang 最初的论文主张引擎要看懂程序结构才能优化,这次把同一思路用到了多轮对话上。第二是评测传统,SGLang 出自 LMSYS,LMSYS 做过 Chatbot Arena,这次也发布了统一评测。用同一套录音和打分比较几种做法,就需要它们走同一种会话协议。第三是技术栈里的位置,占住对话这一层,引擎才不会沦为 OpenAI 兼容接口后面随时能被换掉的执行器。

关于对话状态该落在哪一层,各家给出了不同答案:vLLM-Omni 让引擎只给基础能力、编排留在外面;LiveKit Agents 文档和 Pipecat 项目把这些放在应用层;原生模型把听说时机学进了权重;托管服务把一切都封在里面。具体进度也有差异,比如 vLLM-Omni 已经为部分模型提供断线恢复,SGLang 目前还没有。

各家把会话控制放在不同的层:SGLang 放进编排加引擎,vLLM-Omni 的引擎只给原语这类基础能力、编排留在外面,LiveKit 和 Pipecat 放在应用与编排层,原生模型放进模型本身,托管整栈不对外暴露

代价和胜负

一个抽象被采用的前提,是让用户最常改的东西变容易。SGLang 的抽象适合经常换模型和比架构的团队,但大多数语音产品团队常改的是提示词、工具、业务逻辑和电话接入,这些都在应用层。

现在已经有用户把这个实时接口接进了生产环境(见他的自述),但他在外面自己实现了计费、空闲断连和资源回收。

目前 SGLang 里不同模型对取消、截断和工具的支持仍然不统一,全都不支持断线恢复。状态常驻在服务端以后,SGLang 和 vLLM-Omni 最近都修过不同用户的对话串在一起的问题(SGLang 的修复)。

SGLang 和 vLLM-Omni 争的是上层框架选用哪套规则和推理引擎同步状态。如果上层框架把 SGLang 的会话接口当标准接进来,它就押对了。我们可以观察两个信号:新发布的开放语音模型发布当天 SGLang 能不能跑起来,以及 Pipecat 和 LiveKit 会不会把它接进来而不是绕开。

对于只调 API 的工程师来说,评估锁定风险可以观察两件事:一是会话接口是不是开放的标准,还是在通用接口名下夹着私有扩展;二是会话状态能不能导出、在别处重建。目前 SGLang 和 vLLM-Omni 的实时接口都在通用事件上加了各自的扩展,SGLang 评测客户端就依赖这些扩展事件。谁的会话接口成为上层的标准,就决定了你的对话状态最后放在哪一层、以后换引擎要付多少代价。

鸭哥每日手记

日更的深度AI新闻和分析