<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>StreamAzure 的笔记</title><description>读源码的记录</description><link>https://streamazure.github.io/</link><templateTheme>Firefly</templateTheme><templateThemeVersion>6.16.8</templateThemeVersion><templateThemeUrl>https://github.com/CuteLeaf/Firefly</templateThemeUrl><lastBuildDate>2026年10月7日 03:08:44</lastBuildDate><item><title>Mini-SGLang 源码解析：从 HTTP 到 SSE，5 次消息投递</title><link>https://streamazure.github.io/posts/mini-sglang-request-flow/</link><guid isPermaLink="true">https://streamazure.github.io/posts/mini-sglang-request-flow/</guid><description>从 HTTP 请求进入 API Server 开始，经 ZMQ 投递、tokenizer 分词、scheduler 调度计算，到增量 token 以 SSE chunk 返回客户端的完整链路。</description><pubDate>Tue, 19 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;总体流程&lt;a href=&quot;#总体流程&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;API Server 请求处理总体流程：用户请求经 API Server、Tokenizer、Detokenizer 与多个 Scheduler 流转&quot; loading=&quot;lazy&quot; width=&quot;714&quot; height=&quot;734&quot; src=&quot;/_astro/01-api-server-overview.BZbexuxY_1hq4fb.webp&quot; /&gt;&lt;figcaption&gt;API Server 请求处理总体流程：用户请求经 API Server、Tokenizer、Detokenizer 与多个 Scheduler 流转&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;API Server&lt;a href=&quot;#api-server&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;以 &lt;strong&gt;Qwen/Qwen3-0.6B&lt;/strong&gt; 模型为例，启动 Mini-SGLang 推理服务，并向服务发送一个用户请求：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-X&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;http://localhost:8000/v1/chat/completions&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-H&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;model&quot;: &quot;Qwen/Qwen3-0.6B&quot;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;Hello&quot;}],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;max_tokens&quot;: 64,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;stream&quot;: true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;该请求首先被 &lt;code&gt;server/api_server.py&lt;/code&gt; 中定义的 FastAPI 服务接口处理。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;用户请求投递与响应 token 分发&lt;a href=&quot;#用户请求投递与响应-token-分发&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在理解 LLM 服务的 API Server 设计逻辑之前，我们需要回顾一下传统 Web API 设计。&lt;/p&gt;&lt;p&gt;在传统 Web 服务中，请求进来后，服务器从线程池里取一个线程来处理它，最后完整返回响应结果，并将线程归还到线程池中。在这样的处理模型下，响应结果与其对应的请求天然绑定，不需要额外的判断。&lt;/p&gt;&lt;p&gt;但 LLM 推理引擎的计算特性（即，将多个请求拼接成一个大矩阵一起计算，作为计算结果的 token 可能交错返回：刚刚返回的 token 可能属于请求 1，下一个 token 可能属于请求 2），使我们在设计 API Server 层时，必须考虑这样一个问题：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1. 如何将后端交错返回的 token，正确绑定到对应的请求上？&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在 API Server 中，我们可以为每个请求分配一个编号：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;@app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/v1/chat/completions&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v1_completions&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; OpenAICompletionRequest&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Request&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get_global_state&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;# 全局状态管理&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;uid &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; state.&lt;/span&gt;&lt;span&gt;new_user&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 为新请求分配编号、分配缓冲区 ack_map、分配异步事件 asyncio.Event() 实例&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;StreamingResponse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;state.&lt;/span&gt;&lt;span&gt;stream_with_cancellation&lt;/span&gt;&lt;span&gt;(state.&lt;/span&gt;&lt;span&gt;stream_chat_completions&lt;/span&gt;&lt;span&gt;(uid), request, uid),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# 给 StreamingResponse 中的 stream 绑定 uid&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;media_type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text/event-stream&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这个 &lt;code&gt;uid&lt;/code&gt; 将贯穿整个处理链路：API Server → tokenizer → scheduler → detokenizer → API Server。&lt;/p&gt;&lt;p&gt;在整条处理链路的最后一步，后端将 uid、增量文本、结束标记返回给 API Server：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@dataclass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;UserReply&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BaseFrontendMsg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;uid: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;# 关联请求的 uid&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;incremental_output: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;# 增量文本&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;finished: &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;# 是否结束&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;为请求分配编号之后，还需要考虑：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;如何全局维护编号与请求的对应关系以及其他相关信息？即，如何维护 API Server 的全局状态？&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;FastAPI 提供的接口方法 &lt;code&gt;v1_completions&lt;/code&gt; 应当是无状态的，我们需要在接口方法之外，创建一个能够跨请求共享的运行时管理器，即 &lt;code&gt;FrontendManager&lt;/code&gt;，如图所示。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;FrontendManager 全局状态管理器持有的字段：config、uid_counter、send/recv_tokenizer、ack_map、event_map&quot; loading=&quot;lazy&quot; width=&quot;925&quot; height=&quot;234&quot; src=&quot;/_astro/02-frontend-manager-state.Byza2t0f_Z1U5Lvk.webp&quot; /&gt;&lt;figcaption&gt;FrontendManager 全局状态管理器持有的字段：config、uid_counter、send/recv_tokenizer、ack_map、event_map&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;除了管理请求编号以外，它还包含其他需要被跨请求共享的信息：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@dataclass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;FrontendManager&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 推理服务全局配置，如模型路径、最大并发请求数等&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;config: ServerArgs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# send/recv_tokenizer 是 API Server 与后端之间数据交互的通道&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;send_tokenizer: ZmqAsyncPushQueue[BaseTokenizerMsg]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;recv_tokenizer: ZmqAsyncPullQueue[BaseFrontendMsg]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 编号计数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;uid_counter: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;initialized: &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 维护当前正在处理的多个请求的响应 token 数据和事件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ack_map: Dict[&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;, List[UserReply]] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;default_factory&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;event_map: Dict[&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;, asyncio.Event] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;default_factory&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;当多个请求（要求流式响应）到达时，API Server 需要维护多个 HTTP 长连接。为了避免多个长连接持续轮询是否有新 token 产生，每个请求都在 &lt;code&gt;state.new_user&lt;/code&gt; 方法中向 &lt;code&gt;FrontendManager&lt;/code&gt; 注册一个 &lt;code&gt;asyncio.Event&lt;/code&gt;，并在 &lt;code&gt;ack_map&lt;/code&gt; 中注册获取自己的缓冲区 ，以“生产者-消费者”模式处理后端返回的 token，如图所示：&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;请求在 new_user() 中获取 uid，并注册缓冲区 ack_map 与监听事件 event_map&quot; loading=&quot;lazy&quot; width=&quot;1216&quot; height=&quot;410&quot; src=&quot;/_astro/03-new-user-registration.BF-tacWn_ZCscwR.webp&quot; /&gt;&lt;figcaption&gt;请求在 new_user() 中获取 uid，并注册缓冲区 ack_map 与监听事件 event_map&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;如何将用户请求投递给后端？&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;请求通过 &lt;code&gt;state.new_user()&lt;/code&gt; 完成注册、获得 &lt;code&gt;uid&lt;/code&gt; 后，将被 API Server 进一步包装为 &lt;code&gt;TokenizeMsg&lt;/code&gt; 对象，并通过 &lt;code&gt;send_one&lt;/code&gt; 方法，将请求数据通过 &lt;code&gt;send_tokenizer&lt;/code&gt; 投递到后端，如图所示。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;&lt;span&gt; state.&lt;/span&gt;&lt;span&gt;send_one&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;TokenizeMsg&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;uid&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;uid,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;prompt,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;sampling_params&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;SamplingParams&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;请求投递：await state.send_one(TokenizeMsg(...)) 经 self.send_tokenizer.put(msg) 发往后端&quot; loading=&quot;lazy&quot; width=&quot;1206&quot; height=&quot;376&quot; src=&quot;/_astro/04-request-dispatch-send-one.Bz2BQvex_Z2fs2bi.webp&quot; /&gt;&lt;figcaption&gt;请求投递：await state.send_one(TokenizeMsg(...)) 经 self.send_tokenizer.put(msg) 发往后端&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;在等待后端返回 token 期间，请求在 &lt;code&gt;stream_chat_completions(uid)&lt;/code&gt; 方法内部的&lt;code&gt;wait_for_ack()&lt;/code&gt; 方法中调用 &lt;code&gt;await event.wait()&lt;/code&gt; 挂起，直到事件通知。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;后端 token 返回链路：listen() 写入 ack_map 并唤醒事件，stream_chat_completions 取出增量文本封装为 SSE chunk&quot; loading=&quot;lazy&quot; width=&quot;1213&quot; height=&quot;506&quot; src=&quot;/_astro/05-stream-response-path.DHNhXmsw_ksS3d.webp&quot; /&gt;&lt;figcaption&gt;后端 token 返回链路：listen() 写入 ack_map 并唤醒事件，stream_chat_completions 取出增量文本封装为 SSE chunk&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;4. 后端返回 token 后，如何将其分发给对应请求？&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;请求所等待的事件通知由 &lt;code&gt;FrontendManager&lt;/code&gt; 触发，整体流程如图所示。&lt;/p&gt;&lt;p&gt;&lt;code&gt;FrontendManager&lt;/code&gt; 实例通过 &lt;code&gt;listen()&lt;/code&gt; 方法为所有请求统一监听 token 返回事件。它通过 &lt;code&gt;recv_tokenizer&lt;/code&gt; 与后端直接通信。读到后端返回的一个 &lt;code&gt;UserReply&lt;/code&gt; 对象后，它将该对象放进 &lt;code&gt;ack_map[uid]&lt;/code&gt;，再调用 &lt;code&gt;event_map[msg.uid].set()&lt;/code&gt; 方法唤醒对应的请求。&lt;/p&gt;&lt;p&gt;请求醒来后，继续执行&lt;code&gt;wait_for_ack()&lt;/code&gt;方法：从缓冲区 &lt;code&gt;ack_map[uid]&lt;/code&gt; 中取出数据，清空缓冲区，将数据 &lt;code&gt;yield&lt;/code&gt; 给上一层 &lt;code&gt;stream_chat_completions(uid)&lt;/code&gt; 方法。该方法拿到数据后，解析处理增量文本 &lt;code&gt;ack.incremental_output&lt;/code&gt;，将其包装成 OpenAI 格式的 SSE chunk，然后继续 &lt;code&gt;yield&lt;/code&gt; 给 &lt;code&gt;StreamingResponse&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; ack &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;wait_for_ack&lt;/span&gt;&lt;span&gt;(uid):&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; ack.incremental_output:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;delta[&lt;/span&gt;&lt;span&gt;&quot;content&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ack.incremental_output&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;chunk &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;cmpl-&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;uid&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;object&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;text_completion.chunk&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;choices&quot;&lt;/span&gt;&lt;span&gt;: [{&lt;/span&gt;&lt;span&gt;&quot;delta&quot;&lt;/span&gt;&lt;span&gt;: delta, &lt;/span&gt;&lt;span&gt;&quot;index&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;finish_reason&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt;}],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;yield&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;data: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&lt;span&gt;json.&lt;/span&gt;&lt;span&gt;dumps&lt;/span&gt;&lt;span&gt;(chunk)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;\n\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;最后，&lt;code&gt;StreamingResponse&lt;/code&gt; 是最终的流式响应，作为接口方法 &lt;code&gt;v1_completions&lt;/code&gt; 的返回值返回给用户请求。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;整体上，API Server 的设计可以总结为下图：&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;API Server 设计总结：new_user 注册、send_one 投递、listen 唤醒与 StreamResponse 返回的完整链路&quot; loading=&quot;lazy&quot; width=&quot;1211&quot; height=&quot;486&quot; src=&quot;/_astro/06-api-server-design-summary.BRY1OHaJ_rTypz.webp&quot; /&gt;&lt;figcaption&gt;API Server 设计总结：new_user 注册、send_one 投递、listen 唤醒与 StreamResponse 返回的完整链路&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;ZMQ：与后端 tokenizer 进程通信&lt;a href=&quot;#zmq与后端-tokenizer-进程通信&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在前文中，我们笼统地将负责处理用户请求的部分称为后端。实际上，用户请求首先抵达分词器 tokenizer，经过分词处理后，才会进一步递交给推理引擎进行 token 计算；计算完毕的 token 还需要经过反分词处理，转换成自然语言文本后再返回给 API Server。&lt;/p&gt;&lt;p&gt;API Server 的通信对象是 tokenizer/detokenizer 两个进程，它使用 ZMQ（ZeroMQ）这一消息队列框架作为通信组件。&lt;code&gt;FrontendManager&lt;/code&gt; 所持有的&lt;code&gt;send_tokenizer&lt;/code&gt; 和  &lt;code&gt;recv_tokenizer&lt;/code&gt; 分别是 &lt;code&gt;ZmqAsyncPushQueue&lt;/code&gt; 对象和 &lt;code&gt;ZmqAsyncPullQueue&lt;/code&gt; 对象，即 API Server 这一侧持有的 ZMQ 通信端口。&lt;/p&gt;&lt;p&gt;API Server 发给 tokenizer 的数据被封装为 &lt;code&gt;TokenizeMsg&lt;/code&gt; 对象，包含 uid，请求文本和采样参数：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@dataclass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TokenizeMsg&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BaseTokenizerMsg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;uid: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;text: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; List[Dict[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;sampling_params: SamplingParams&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;以本章开头提供的请求参数为例，被封装为 &lt;code&gt;TokenizeMsg&lt;/code&gt; 对象再经序列化之后的内容如下所示：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;__type__&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;TokenizeMsg&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;uid&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;text&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;role&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;user&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;content&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;hello&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;sampling_params&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;__type__&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;SamplingParams&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;temperature&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;top_k&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;-1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;top_p&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;ignore_eos&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;max_tokens&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;除了正常的用户 Prompt 以外，用户还可能发送取消请求。取消请求将被封装为 &lt;code&gt;AbortMsg&lt;/code&gt;。取消请求不会被分配新的 &lt;code&gt;uid&lt;/code&gt;，而是直接携带被取消的请求的 &lt;code&gt;uid&lt;/code&gt;，同样通过 ZMQ 发送给 tokenizer。&lt;/p&gt;&lt;p&gt;值得注意的是，后端通过 ZMQ 向 API Server 返回消息时，并非对称地以 &lt;code&gt;DetokenizeMsg&lt;/code&gt; 对象格式返回，而是使用 &lt;code&gt;BatchFrontendMsg&lt;/code&gt; 对象，其&lt;code&gt;data&lt;/code&gt; 字段包含多个用户的结果（&lt;code&gt;List[UserReply]&lt;/code&gt;） ：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@dataclass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BatchFrontendMsg&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BaseFrontendMsg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;data: List[BaseFrontendMsg]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# UserReply 是 BaseFrontendMsg 的子类&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;FrontendManager&lt;/code&gt; 的 &lt;code&gt;listen&lt;/code&gt; 方法将 &lt;code&gt;data&lt;/code&gt; 字段拆开，按 uid 将 &lt;code&gt;Reply&lt;/code&gt; 对象放回到对应请求的 &lt;code&gt;ack_map&lt;/code&gt; 中。&lt;/p&gt;&lt;p&gt;那么 &lt;code&gt;DetokenizeMsg&lt;/code&gt; 在哪里被使用呢？事实上，它存在于后端，是 &lt;code&gt;scheduler&lt;/code&gt; 发给 &lt;code&gt;detokenizer&lt;/code&gt; 的数据封装格式，不会被 API Server 直接收到：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@dataclass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DetokenizeMsg&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BaseTokenizerMsg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;uid: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;next_token: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;finished: &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;detokenizer&lt;/code&gt; 负责将其中的 &lt;code&gt;next_token&lt;/code&gt; 转换为可读文本，包装成 &lt;code&gt;UserReply&lt;/code&gt;后再发回给 API Server。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;前端管理器视角的完整消息链路：注册缓冲区、send_tokenizer 投递、接收返回 token、yield 增量文本返回 chunk&quot; loading=&quot;lazy&quot; width=&quot;1276&quot; height=&quot;741&quot; src=&quot;/_astro/07-full-message-chain.D-4vEgO2_vCyns.webp&quot; /&gt;&lt;figcaption&gt;前端管理器视角的完整消息链路：注册缓冲区、send_tokenizer 投递、接收返回 token、yield 增量文本返回 chunk&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;综上，API Server 与后端通信的完整消息链路如图所示：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;API Server 收 HTTP JSON 请求后，将其内容封装为 &lt;code&gt;TokenizeMsg&lt;/code&gt; 对象，通过 ZMQ 发给 tokenizer。&lt;/li&gt;
&lt;li&gt;tokenizer 把其中的文本内容转成 &lt;code&gt;input_ids&lt;/code&gt;，包装为 &lt;code&gt;UserMsg&lt;/code&gt; 对象，通过 ZMQ 发给 scheduler。&lt;/li&gt;
&lt;li&gt;scheduler 推理出 next_token 后，封装为 &lt;code&gt;DetokenizeMsg&lt;/code&gt; 对象，通过 ZMQ 发给 detokenizer。&lt;/li&gt;
&lt;li&gt;detokenizer 把 token id 转换为可读文本，并封装为 &lt;code&gt;UserReply&lt;/code&gt; 对象后，通过 ZMQ 发回 API Server。&lt;/li&gt;
&lt;li&gt;最后 API Server 通过 &lt;code&gt;recv_tokenizer&lt;/code&gt; 取出 &lt;code&gt;BatchFrontendMsg&lt;/code&gt;，拆出其中的多个 &lt;code&gt;UserReply&lt;/code&gt; 对象，处理成 SSE chunk 后，最终返回给 HTTP 客户端。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Tokenizer &amp;amp; Detokenizer&lt;a href=&quot;#tokenizer--detokenizer&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;默认情况下，tokenizer 和 detokenizer 属于同一个进程，在 &lt;code&gt;tokenizer/server.py&lt;/code&gt; 中的 &lt;code&gt;tokenize_worker&lt;/code&gt; 一并初始化（即 &lt;code&gt;TokenizeManager&lt;/code&gt; 和 &lt;code&gt;DetokenizeManager&lt;/code&gt;）。&lt;/p&gt;&lt;p&gt;&lt;code&gt;tokenize_worker&lt;/code&gt; 管理一个消息循环，持续监听来自 ZMQ 的消息。为减少读取开销，它采用批量收集的方式，尽量多地收取队列中已有的消息后再进行处理，但不会刻意凑满某个消息数量：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;&lt;span&gt;(pending_msg) &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; local_bs &lt;/span&gt;&lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt;&lt;span&gt; recv_listener.&lt;/span&gt;&lt;span&gt;empty&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pending_msg.&lt;/span&gt;&lt;span&gt;extend&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_unwrap_msg&lt;/span&gt;&lt;span&gt;(recv_listener.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;()))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;消息数量达到 &lt;code&gt;local_bs&lt;/code&gt;（推理服务启动时指定，默认为 1），或队列已空时，停止消息收集，开始处理。&lt;/p&gt;&lt;p&gt;它接收的消息分为以下三类：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;来自 API Server 的 &lt;code&gt;tokenize_msg&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;来自 API Server 的 &lt;code&gt;abort_msg&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;来自 Scheduler 的 &lt;code&gt;detokenize_msg&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;对于 &lt;code&gt;tokenize_msg&lt;/code&gt; 和 &lt;code&gt;detokenize_msg&lt;/code&gt;，&lt;code&gt;tokenize_worker&lt;/code&gt; 分别调用 &lt;code&gt;TokenizeManager.tokenize()&lt;/code&gt; 和 &lt;code&gt;DetokenizeManager.detokenize()&lt;/code&gt; 进行处理，并将 token 结果分别封装为 &lt;code&gt;UserMsg&lt;/code&gt; 和 &lt;code&gt;UserReply&lt;/code&gt; ，多个 token 结果合并到一个消息对象，通过 ZMQ 投递到对应接收方。&lt;/p&gt;&lt;p&gt;&lt;code&gt;abort_msg&lt;/code&gt; 的处理方式与 &lt;code&gt;tokenize_msg&lt;/code&gt; 基本相同，但不会被封装为 &lt;code&gt;UserMsg&lt;/code&gt;，而是封装为 &lt;code&gt;AbortBackendMsg&lt;/code&gt;，该对象内只包含一个 &lt;code&gt;uid&lt;/code&gt; 字段，用以表示要被取消的目标请求。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Scheduler&lt;a href=&quot;#scheduler&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;一个请求的调度与计算流程&lt;a href=&quot;#一个请求的调度与计算流程&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;假设一个用户请求经过 API Server 与 tokenizer 的处理，以 &lt;code&gt;UserMsg&lt;/code&gt; 格式经由 ZMQ 抵达 scheduler。在 scheduler 中，它需要经过 prefill 阶段（并在该阶段建立、利用 KV Cache），完成用户输入 prompt 的注意力矩阵计算后，再进入 decode 阶段，逐个产生新 token，并持续以 &lt;code&gt;DetoknizeMsg&lt;/code&gt; 的格式返回给 detokenizer。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;预处理&lt;/strong&gt;。scheduler 在调度循环中，从 ZMQ 取出上文所述的 &lt;code&gt;UserMsg&lt;/code&gt;。&lt;code&gt;UserMsg&lt;/code&gt; 首先在 &lt;code&gt;_process_one_msg&lt;/code&gt; 方法中被处理。我们需要保证用户输入的 prompt 长度 + 最大生成长度不会超过模型能承受的最大序列长度。例如，模型最大上下文为 8192，用户 prompt 已有 8000 个 token，则最多只能再生成 192 个 token。即使用户请求中已指定 &lt;code&gt;max_tokens=1024&lt;/code&gt;，scheduler 也只从模型最大上下文角度考虑。此外，如果用户 prompt 本身 token 数就已经超过模型最大上下文，则会被直接丢弃，不做处理：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;input_len, max_seq_len &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(msg.input_ids), &lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.engine.max_seq_len&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;max_output_len &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; max_seq_len &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; input_len&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; max_output_len &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 输入 prompt 本身已经超过最大上下文长度，无生成 token 的空间，直接丢弃&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; logger.&lt;/span&gt;&lt;span&gt;warning_rank0&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;Input sequence length &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;input_len&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; exceeds &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;max_seq_len&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;, &quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;request &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;msg.uid&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; is dropped.&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; msg.sampling_params.max_tokens &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; max_output_len:&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 输入 prompt 未超最大上下文长度，但无法满足用户指定的 max_tokens，&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 以 max_output_len 为 max_tokens&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;msg.sampling_params.max_tokens &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; max_output_len&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;logger.&lt;/span&gt;&lt;span&gt;warning_rank0&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;Adjust max_tokens to &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;max_output_len&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; for request &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;msg.uid&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;.&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Prefill 调度&lt;/strong&gt;。通过上下文长度检查的 &lt;code&gt;UserMsg&lt;/code&gt; 不会马上进行 prefill 计算，而是先被转换为 &lt;code&gt;PendingReq&lt;/code&gt; 对象，放入到 pending 队列中，归属&lt;code&gt;PrefillManager&lt;/code&gt; 管理，等待 scheduler 的下一批次调度。当 &lt;code&gt;schedule_next_batch&lt;/code&gt; 方法被调用时，pending 队列中的每一个 pending 请求都会被尝试加入（ &lt;code&gt;try_add_one(pending_req)&lt;/code&gt; ）到本轮 prefill batch 中。&lt;/p&gt;&lt;p&gt;不是每个 pending 请求都能一次性完成 prefill 计算。对于长 prompt，可能要分多个轮次计算，每次只 prefill 一部分。因此，&lt;code&gt;try_add_one&lt;/code&gt;方法考虑两种情况：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pending_req&lt;/code&gt; 是之前已处理过一部分的长 prompt 请求。此时，该请求的相关 KV Cache 资源已经被分配过，则直接复用，继续处理当前部分。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pending_req&lt;/code&gt; 是全新的请求。新请求要进入 prefill 阶段，必须先通过 &lt;code&gt;_try_allocate_one()&lt;/code&gt; 申请资源：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 &lt;code&gt;CacheManager&lt;/code&gt; 查 &lt;code&gt;prefix cache&lt;/code&gt;，得到 &lt;code&gt;cache_handle&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;向 &lt;code&gt;TableManager&lt;/code&gt; 申请 &lt;code&gt;table_idx&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;判断 KV cache 空间是否充足；&lt;/li&gt;
&lt;li&gt;如果有命中的 prefix，还会把匹配的 token/page 信息写进 token pool/page table。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;若资源不足，比如 &lt;code&gt;TableManager&lt;/code&gt; 已无空闲槽位，或 KV Cache 空间不足，则该方法返回 &lt;code&gt;None&lt;/code&gt;，最终结束本轮 prefill 调度，将调度结果包装为 &lt;code&gt;Batch(reqs=reqs, phase=&quot;prefill&quot;)&lt;/code&gt; 返回。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;接下来，prefill batch 送入 &lt;code&gt;_prepare_batch&lt;/code&gt; 方法中的预处理管线，转换成 GPU 可执行的 &lt;code&gt;ForwardInput&lt;/code&gt;。该预处理管线能处理 prefill 和 decode 两类请求 batch，步骤包括：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pad_batch&lt;/code&gt;：将 batch 内的请求 padding 到统一对齐长度，方便 CUDA Graph 以固定 shape 进行录制和回放。它只影响 decode batch。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;allocate_paged&lt;/code&gt;：为每个请求在 paged KV Cache 中分配物理页。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_make_positions&lt;/code&gt;：生成每个 token 的位置编码，用于 attention 计算过程中的 RoPE 等位置编码计算。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_make_input_tuple&lt;/code&gt;：构建输入 token 的逻辑索引。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;batch.out_loc&lt;/code&gt;：通过页表将逻辑索引映射到实际 KV Cache 的物理地址。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_make_write_tuple&lt;/code&gt;：构建输出 token 要写回的逻辑索引（即新生成的 token 写回到 token pool 的位置）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prepare_metadata&lt;/code&gt;：准备 attention kernel 需要的元数据，如 page table、cache 长度等，供 kernel 调度使用。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;上述处理完成后，attention 层可以知道这批请求应该怎么读对应 cache、怎么计算 RoPE、怎么计算 prefill/decode。最后，返回一个 &lt;code&gt;ForwardInput&lt;/code&gt; 对象，包含请求 batch、请求的采样参数、input_tuple、write_tuple。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Prefill 批处理&lt;/strong&gt;。&lt;code&gt;Engine.forward_batch()&lt;/code&gt; 方法负责 prefill 的实际计算。Engine 将当前 batch 放入全局上下文，从而模型层可以通过 &lt;code&gt;get_global_ctx()&lt;/code&gt; 看到当前 batch 是 prefill batch 还是 decode batch。对于 prefill batch，直接进行模型前馈计算（&lt;code&gt;model.forward()&lt;/code&gt;）。&lt;/p&gt;&lt;p&gt;举个例子，假设当前计算的 prefill batch 只包含用户请求 A 和 B，其中：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;请求 A 的 prompt 有 4 个 token：&lt;code&gt;A0 A1 A2 A3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;请求 B 的 prompt 有 3 个 token：&lt;code&gt;B0 B1 B2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;为了计算效率，这些 token 会被拼接成一个输入序列 &lt;code&gt;A0 A1 A2 A3 B0 B1 B2&lt;/code&gt;。Scheduler 所准备的元数据将被同时传入，以区分每个请求的 token 边界，如 &lt;code&gt;[0, 4, 7]&lt;/code&gt;，表示请求 A 的 token 范围为 &lt;code&gt;[0, 4)&lt;/code&gt;，请求 B 的 token 范围为 &lt;code&gt;[4, 7)&lt;/code&gt;。Attention kernel 会按这些边界分别计算两条序列。请求 A 的 token 只能看到请求 A 内部的历史 token；请求 B 的 token 只能看到请求 B 内部的历史 token，不会看到请求 A 的 token。&lt;/p&gt;&lt;p&gt;在 prefill 阶段，我们要为每个请求生成第一个输出 token。根据 Attention 计算原理，只需要每个请求最后一个位置的 hidden state：请求 A 用 &lt;code&gt;h3&lt;/code&gt;（即模型看完 &lt;code&gt;A0...A3&lt;/code&gt; 后的状态），请求 B 用 &lt;code&gt;h6&lt;/code&gt;（即模型看完 &lt;code&gt;B0...B2&lt;/code&gt; 后的状态）。然后把 &lt;code&gt;h3&lt;/code&gt; 和 &lt;code&gt;h6&lt;/code&gt; 分别送进 &lt;code&gt;lm_head&lt;/code&gt;（Attention 架构中的线性转换层），得到两个词表 &lt;code&gt;logits&lt;/code&gt;：第一个 &lt;code&gt;logits&lt;/code&gt; 用来采样请求 A 的下一个 token，第二个 &lt;code&gt;logits&lt;/code&gt; 用来采样请求 B 的下一个 token。&lt;/p&gt;&lt;p&gt;从而，&lt;code&gt;model.forward()&lt;/code&gt; 最终返回 &lt;code&gt;logits&lt;/code&gt;。&lt;code&gt;logits&lt;/code&gt; 随后被送入采样流程，确定生成的 token，并将新 token 按先前确定的写回位置 &lt;code&gt;write_tuple&lt;/code&gt; 写回到 GPU 上的 token_pool 中，以备进行下一轮 decode。&lt;/p&gt;&lt;p&gt;&lt;code&gt;forward&lt;/code&gt; 完毕后，当前 prefill batch 中还能继续 decode （如还未达到最大长度）的请求将会被纳入到 &lt;code&gt;decode_manager&lt;/code&gt; 管理，在下一轮 decode batch 调度中执行 decode 计算，继续生成新的 token。&lt;/p&gt;&lt;p&gt;注意，这一轮 prefill 计算中，每个请求都生成了一个新的 token。按前文所述，这个新 token 需要被 detokenizer 处理后转换为增量文本，返回给 API Server。因此，最后还需要执行 &lt;code&gt;_process_last_data&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;新 token 返回&lt;/strong&gt;。在 &lt;code&gt;_process_last_data&lt;/code&gt; 中，它按 batch 顺序遍历请求，完成新 token 与原始请求的绑定：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; i, req &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;enumerate&lt;/span&gt;&lt;span&gt;(batch.reqs):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;next_token &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; next_tokens_cpu[i]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 获取当前请求的新 token&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# next_tokens_cpu[i] 和 batch.reqs[i] 是一一对应的&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;req.&lt;/span&gt;&lt;span&gt;append_host&lt;/span&gt;&lt;span&gt;(next_token.&lt;/span&gt;&lt;span&gt;unsqueeze&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 将新 token 追加到该请求在 CPU 侧的 token 序列中&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;next_token &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(next_token.&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 将 tensor 形式的 token id 转换为普通 int&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;finished &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt; req.can_decode&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 是否达到长度限制，要结束生成&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt; req.sampling_params.ignore_eos:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# 用户是否忽略 EOS，如果不忽略，则模型生成 EOS 时，视为全部内容已经生成完毕&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;finished &lt;/span&gt;&lt;span&gt;|=&lt;/span&gt;&lt;span&gt; next_token &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.eos_token_id&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;reply.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;DetokenizeMsg&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;uid&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;req.uid, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;next_token&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;next_token, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;finished&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;finished))&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 将 token 封装为发给 detokenizer 的消息&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;运行时状态管理&lt;a href=&quot;#运行时状态管理&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Scheduler 作为负责调度所有请求的 token 计算。某一时刻，Scheduler 的调度循环中同时存在以下处于不同处理阶段的请求：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;请求刚刚开始处理，处于 prefill 阶段；&lt;/li&gt;
&lt;li&gt;请求已进入 decode 阶段（正在逐 token 生成）；&lt;/li&gt;
&lt;li&gt;请求将要被取消。&lt;/li&gt;
&lt;li&gt;请求已结束，需要释放 KV cache。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Scheduler 需要管理这些请求所处的阶段、资源占用情况、并决定下一轮计算应执行哪些请求。SGLang 的核心机制 Radix Cache 也在 Scheduler 的 KV cache 管理中体现。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item></channel></rss>