<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>第三章 请求路径 on Machine Learning 学习笔记</title><link>https://kingye.me/study-ml/docs/book/sglang-internals/part2-request-runtime/request-path/</link><description>Recent content in 第三章 请求路径 on Machine Learning 学习笔记</description><generator>Hugo</generator><language>en</language><atom:link href="https://kingye.me/study-ml/docs/book/sglang-internals/part2-request-runtime/request-path/index.xml" rel="self" type="application/rss+xml"/><item><title>3.1 从 /v1/chat/completions 到 Scheduler</title><link>https://kingye.me/study-ml/docs/book/sglang-internals/part2-request-runtime/request-path/from-chat-completions-to-scheduler/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kingye.me/study-ml/docs/book/sglang-internals/part2-request-runtime/request-path/from-chat-completions-to-scheduler/</guid><description>&lt;h1 id="从-v1chatcompletions-到-scheduler"&gt;从 &lt;code&gt;/v1/chat/completions&lt;/code&gt; 到 &lt;code&gt;Scheduler&lt;/code&gt;&lt;a class="anchor" href="#%e4%bb%8e-v1chatcompletions-%e5%88%b0-scheduler"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id="这章解决什么问题"&gt;这章解决什么问题&lt;a class="anchor" href="#%e8%bf%99%e7%ab%a0%e8%a7%a3%e5%86%b3%e4%bb%80%e4%b9%88%e9%97%ae%e9%a2%98"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;如果你只从 API 表面看 SGLang，很容易把 &lt;code&gt;/v1/chat/completions&lt;/code&gt; 理解成一个普通的 FastAPI 路由：接收一个 JSON，请求进入模型，最后吐出字符串。这个理解能帮助你调用接口，但几乎不能帮助你读源码。因为从源码角度看，这条链路真正重要的不是 HTTP，而是请求对象怎样一步步从“协议形态”收缩成“运行时形态”。&lt;/p&gt;
&lt;p&gt;更准确地说，这一章主要回答四个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;OpenAI-compatible 请求是在什么地方结束协议层处理的。&lt;/li&gt;
&lt;li&gt;统一的 runtime 请求对象是怎样建立起来的。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TokenizerManager&lt;/code&gt; 在这条链里到底承担了什么职责。&lt;/li&gt;
&lt;li&gt;请求到了 scheduler 以后，是在哪一步真正变成运行时工作单元 &lt;code&gt;Req&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;沿着这条链读，更稳的做法不是把它看成五个彼此独立的函数，而是看成一组按顺序演化的请求对象：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ChatCompletionRequest&lt;/code&gt; -&amp;gt; &lt;code&gt;GenerateReqInput&lt;/code&gt; -&amp;gt; &lt;code&gt;ReqState established&lt;/code&gt; -&amp;gt; &lt;code&gt;TokenizedGenerateReqInput&lt;/code&gt; -&amp;gt; &lt;code&gt;Req&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="一张图先看完整主线"&gt;一张图先看完整主线&lt;a class="anchor" href="#%e4%b8%80%e5%bc%a0%e5%9b%be%e5%85%88%e7%9c%8b%e5%ae%8c%e6%95%b4%e4%b8%bb%e7%ba%bf"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;如果先不陷进局部代码细节，这条链更适合先压成“分层边界”来看：&lt;/p&gt;
&lt;pre class="mermaid"&gt;flowchart TB
 subgraph Protocol[&amp;#34;协议层&amp;#34;]
 A[&amp;#34;/v1/chat/completions&amp;lt;br/&amp;gt;ChatCompletionRequest&amp;#34;]
 end

 subgraph APIServer[&amp;#34;API server / serving 层&amp;#34;]
 B[&amp;#34;OpenAIServingBase.handle_request&amp;#34;]
 C[&amp;#34;OpenAIServingChat._convert_to_internal_request&amp;#34;]
 D[&amp;#34;GenerateReqInput&amp;#34;]
 end

 subgraph Bridge[&amp;#34;TokenizerManager 边界层&amp;#34;]
 E[&amp;#34;ReqState&amp;#34;]
 F[&amp;#34;TokenizerManager._tokenize_one_request&amp;#34;]
 G[&amp;#34;TokenizedGenerateReqInput&amp;#34;]
 end

 subgraph Runtime[&amp;#34;Scheduler / runtime 层&amp;#34;]
 H[&amp;#34;Scheduler.handle_generate_request&amp;#34;]
 I[&amp;#34;Req&amp;#34;]
 J[&amp;#34;grammar / queue / batch&amp;#34;]
 end

 A --&amp;gt; B --&amp;gt; C --&amp;gt; D
 D --&amp;gt; E
 D --&amp;gt; F --&amp;gt; G
 G --&amp;gt; H --&amp;gt; I --&amp;gt; J&lt;/pre&gt;&lt;p&gt;这张图最值得记住的一点是：&lt;code&gt;TokenizerManager&lt;/code&gt; 不是简单的“协议后、scheduler 前”中转站，而是一条正式边界。&lt;code&gt;ReqState&lt;/code&gt; 和 &lt;code&gt;TokenizedGenerateReqInput&lt;/code&gt; 都从这里分化出来。&lt;/p&gt;</description></item><item><title>3.2 Streaming 与回包组装</title><link>https://kingye.me/study-ml/docs/book/sglang-internals/part2-request-runtime/request-path/streaming-and-response-assembly/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kingye.me/study-ml/docs/book/sglang-internals/part2-request-runtime/request-path/streaming-and-response-assembly/</guid><description>&lt;h1 id="streaming-与回包组装"&gt;Streaming 与回包组装&lt;a class="anchor" href="#streaming-%e4%b8%8e%e5%9b%9e%e5%8c%85%e7%bb%84%e8%a3%85"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;3.1&lt;/code&gt; 讲到请求被 scheduler 接管为止。这一节补的是后半段：scheduler 已经产出了 token 级结果以后，结果怎样回到 API server，怎样被逐步解码成文本，又怎样被组装成 streaming chunk 或完整响应。&lt;/p&gt;
&lt;p&gt;这一节只关注四个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;scheduler 输出的到底是什么。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DetokenizerManager&lt;/code&gt; 怎样把 token ids 变成字符串增量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TokenizerManager&lt;/code&gt; 怎样把返回结果折叠回 &lt;code&gt;ReqState&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;serving 层怎样基于同一份后端状态分出 streaming 和 non-streaming 两种响应路径。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="一张图先看返回主线"&gt;一张图先看返回主线&lt;a class="anchor" href="#%e4%b8%80%e5%bc%a0%e5%9b%be%e5%85%88%e7%9c%8b%e8%bf%94%e5%9b%9e%e4%b8%bb%e7%ba%bf"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;先把这一节要讲的链路压成一张图：&lt;/p&gt;
&lt;pre class="mermaid"&gt;flowchart TB
 A[&amp;#34;Scheduler&amp;lt;br/&amp;gt;BatchTokenIDOutput&amp;#34;] --&amp;gt; B[&amp;#34;DetokenizerManager&amp;lt;br/&amp;gt;增量 detokenize&amp;#34;]
 B --&amp;gt; C[&amp;#34;BatchStrOutput&amp;#34;]
 C --&amp;gt; D[&amp;#34;TokenizerManager._handle_batch_output&amp;lt;br/&amp;gt;回填 ReqState&amp;#34;]
 D --&amp;gt; E[&amp;#34;TokenizerManager._wait_one_response&amp;#34;]
 E --&amp;gt; F[&amp;#34;streaming SSE chunks&amp;#34;]
 E --&amp;gt; G[&amp;#34;full ChatCompletionResponse&amp;#34;]&lt;/pre&gt;&lt;p&gt;这张图里最重要的一点是：scheduler 并不直接回文本。它先回 token ids 和一组伴随的 meta 信息，然后由 detokenizer 和 API server 侧状态层一起完成后面的收口。&lt;/p&gt;
&lt;p&gt;如果把这条返回链再压成“对象怎样变化”，可以得到更清楚的第二张图：&lt;/p&gt;
&lt;pre class="mermaid"&gt;flowchart LR
 A[&amp;#34;BatchTokenIDOutput&amp;lt;br/&amp;gt;token ids + finish/meta&amp;#34;] --&amp;gt; B[&amp;#34;BatchStrOutput&amp;lt;br/&amp;gt;text delta + output_ids + meta&amp;#34;]
 B --&amp;gt; C[&amp;#34;ReqState&amp;lt;br/&amp;gt;text / output_ids / time_stats&amp;#34;]
 C --&amp;gt; D[&amp;#34;streaming chunk&amp;#34;]
 C --&amp;gt; E[&amp;#34;full response&amp;#34;]&lt;/pre&gt;&lt;p&gt;第一张图强调进程边界，第二张图强调返回对象的变化顺序。两张图合起来，基本就把这一节的主线钉住了。&lt;/p&gt;</description></item><item><title>3.3 Session、timeout 与 abort 分叉</title><link>https://kingye.me/study-ml/docs/book/sglang-internals/part2-request-runtime/request-path/session-timeout-and-abort/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kingye.me/study-ml/docs/book/sglang-internals/part2-request-runtime/request-path/session-timeout-and-abort/</guid><description>&lt;h1 id="sessiontimeout-与-abort-分叉"&gt;Session、timeout 与 abort 分叉&lt;a class="anchor" href="#sessiontimeout-%e4%b8%8e-abort-%e5%88%86%e5%8f%89"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;3.1&lt;/code&gt; 和 &lt;code&gt;3.2&lt;/code&gt; 讲的是最小主链：请求怎样进入 runtime，结果又怎样回到协议表面。但真实系统里并不只有这条标准路径。session、timeout 和 abort 都会改变请求对象的命运，而且它们改变的不是“输出样式”，而是请求到底还能不能沿主链继续走下去。&lt;/p&gt;
&lt;p&gt;这一节只处理三类正式分叉：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;session 会改写 &lt;code&gt;Req&lt;/code&gt; 的构造方式；&lt;/li&gt;
&lt;li&gt;timeout 会改写请求结束的阶段；&lt;/li&gt;
&lt;li&gt;abort 会从 API server、client 或 scheduler 不同方向切断主链。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="一张图先看分叉"&gt;一张图先看分叉&lt;a class="anchor" href="#%e4%b8%80%e5%bc%a0%e5%9b%be%e5%85%88%e7%9c%8b%e5%88%86%e5%8f%89"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;先把这几条分叉压成一张图：&lt;/p&gt;
&lt;pre class="mermaid"&gt;flowchart TB
 A[&amp;#34;标准生成请求&amp;#34;] --&amp;gt; B[&amp;#34;进入 tokenizer / scheduler 主链&amp;#34;]
 A --&amp;gt; C[&amp;#34;带 session_params&amp;#34;]
 C --&amp;gt; D[&amp;#34;Session.create_req&amp;lt;br/&amp;gt;复用前缀 / append / replace&amp;#34;]
 A --&amp;gt; E[&amp;#34;client disconnect / explicit abort&amp;#34;]
 E --&amp;gt; F[&amp;#34;TokenizerManager.abort_request&amp;#34;]
 A --&amp;gt; G[&amp;#34;waiting timeout / running timeout&amp;#34;]
 G --&amp;gt; H[&amp;#34;Scheduler abort paths&amp;#34;]&lt;/pre&gt;&lt;p&gt;这张图里最重要的一点是：session、timeout 和 abort 都不是主链末尾的补丁，而是会直接改写请求路径和对象状态的正式分叉。&lt;/p&gt;</description></item><item><title>3.4 Batch、多 worker 与多模态路径</title><link>https://kingye.me/study-ml/docs/book/sglang-internals/part2-request-runtime/request-path/batch-worker-and-multimodal/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kingye.me/study-ml/docs/book/sglang-internals/part2-request-runtime/request-path/batch-worker-and-multimodal/</guid><description>&lt;h1 id="batch多-worker-与多模态路径"&gt;Batch、多 worker 与多模态路径&lt;a class="anchor" href="#batch%e5%a4%9a-worker-%e4%b8%8e%e5%a4%9a%e6%a8%a1%e6%80%81%e8%b7%af%e5%be%84"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;3.1&lt;/code&gt; 和 &lt;code&gt;3.2&lt;/code&gt; 都是按“单请求、单 worker、纯文本”这个最小闭环来讲的。这一节补的是把这条闭环放大之后会发生什么：当请求变成 batch、当 tokenizer worker 不止一个、当输入不再只有文本，这条路径会在哪些 handoff 点上变形。&lt;/p&gt;
&lt;p&gt;这一节只关心三个变化：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;batch 如何改变发送和回包方式；&lt;/li&gt;
&lt;li&gt;多 tokenizer worker 如何改变 request routing 和 response routing；&lt;/li&gt;
&lt;li&gt;多模态输入如何改变 tokenization 之前的准备阶段。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="一张图先看放大后的主线"&gt;一张图先看放大后的主线&lt;a class="anchor" href="#%e4%b8%80%e5%bc%a0%e5%9b%be%e5%85%88%e7%9c%8b%e6%94%be%e5%a4%a7%e5%90%8e%e7%9a%84%e4%b8%bb%e7%ba%bf"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre class="mermaid"&gt;flowchart TB
 A[&amp;#34;GenerateReqInput&amp;lt;br/&amp;gt;batch / multimodal&amp;#34;] --&amp;gt; B[&amp;#34;TokenizerManager&amp;#34;]
 B --&amp;gt; C[&amp;#34;single request path&amp;#34;]
 B --&amp;gt; D[&amp;#34;batch request path&amp;#34;]
 B --&amp;gt; E[&amp;#34;multi-worker routing&amp;#34;]
 B --&amp;gt; F[&amp;#34;mm_processor / encoder path&amp;#34;]
 D --&amp;gt; G[&amp;#34;BatchTokenizedGenerateReqInput&amp;#34;]
 E --&amp;gt; H[&amp;#34;http_worker_ipc / SenderWrapper&amp;#34;]
 F --&amp;gt; I[&amp;#34;mm_inputs / input_ids rewrite&amp;#34;]&lt;/pre&gt;&lt;p&gt;这张图里最重要的一点是：放大后的路径不是“原主链复制很多份”。一旦进入 batch、多 worker 或 multimodal 模式，输入对象、发送方式和回包对位方式都会发生变化。&lt;/p&gt;</description></item><item><title>3.5 Embedding 与 Reranking 路径</title><link>https://kingye.me/study-ml/docs/book/sglang-internals/part2-request-runtime/request-path/embedding-and-reranking-path/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kingye.me/study-ml/docs/book/sglang-internals/part2-request-runtime/request-path/embedding-and-reranking-path/</guid><description>&lt;h1 id="embedding-与-reranking-路径"&gt;Embedding 与 Reranking 路径&lt;a class="anchor" href="#embedding-%e4%b8%8e-reranking-%e8%b7%af%e5%be%84"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;3.1&lt;/code&gt; 到 &lt;code&gt;3.4&lt;/code&gt; 讲的都是生成式请求：模型一步步生成 token，直到 stop 或 length 触发。但 SGLang 还支持另外两类完全不同的执行路径：&lt;strong&gt;Embedding&lt;/strong&gt;（把文本压缩成向量）和 &lt;strong&gt;Reranking&lt;/strong&gt;（对查询-文档对打分）。这两类路径没有生成循环，不需要 KV cache，执行逻辑和返回对象都和生成路径有本质差异。&lt;/p&gt;
&lt;p&gt;这一节回答三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Embedding 和 Reranking 请求走的是什么路径，和生成请求有哪些分叉；&lt;/li&gt;
&lt;li&gt;ModelRunner 在这两类请求里实际执行的是什么；&lt;/li&gt;
&lt;li&gt;输出对象是什么形态，怎样回到 API 表面。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="生成-vs-embedding两种完全不同的任务模式"&gt;生成 vs Embedding：两种完全不同的任务模式&lt;a class="anchor" href="#%e7%94%9f%e6%88%90-vs-embedding%e4%b8%a4%e7%a7%8d%e5%ae%8c%e5%85%a8%e4%b8%8d%e5%90%8c%e7%9a%84%e4%bb%bb%e5%8a%a1%e6%a8%a1%e5%bc%8f"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;先把两种模式的差异压成一张对比表：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;生成（Generation）&lt;/th&gt;
 &lt;th&gt;Embedding&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;任务目标&lt;/td&gt;
 &lt;td&gt;输出 token 序列&lt;/td&gt;
 &lt;td&gt;输出向量表示&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;执行方式&lt;/td&gt;
 &lt;td&gt;prefill + decode 循环&lt;/td&gt;
 &lt;td&gt;一次 forward pass&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;KV cache&lt;/td&gt;
 &lt;td&gt;需要（decode 复用前缀）&lt;/td&gt;
 &lt;td&gt;不需要&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;输出&lt;/td&gt;
 &lt;td&gt;token ids / 文本&lt;/td&gt;
 &lt;td&gt;浮点向量 &lt;code&gt;[hidden_size]&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;API 入口&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/v1/chat/completions&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/v1/embeddings&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;模型类型&lt;/td&gt;
 &lt;td&gt;decoder-only LLM&lt;/td&gt;
 &lt;td&gt;encoder-only 或 decoder-only&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;最重要的一点是：Embedding 请求&lt;strong&gt;只有 prefill，没有 decode 循环&lt;/strong&gt;。模型跑完一次前向，从 last_hidden_state 或 pooler_output 里取出向量，直接回包。因为没有 decode，KV cache 在这条路径上几乎没有价值。&lt;/p&gt;</description></item></channel></rss>