非生成请求、health_generate 与控制请求分叉#

这章解决什么问题#

前面的 request lifecycle 基本都围绕生成请求在讲:请求进入、分词、排队、forward、detokenize、回包。但真实服务里并不是所有请求都长成这条路径。还有一类非常重要的分叉:

  • embedding / classify / score / rerank 这类非生成请求
  • health_generate
  • open_session / close_session
  • abort_request
  • pause_generation

如果不把这些分叉讲出来,request lifecycle 就仍然会默认“所有请求都要穿过完整生成闭环”,这对一本文字上已经开始变厚的技术书来说是不够的。

为什么这些分叉值得单独成章#

因为它们不是“边缘接口”,而是在运行时里承担不同控制职责:

  • embedding / classify / rerank 会把请求送进不同的内部对象或输出路径。
  • health_generate 用来探测运行时是否还可推进。
  • session / abort / pause 更直接进入控制面,而不是普通生成流。

也就是说,这里讨论的不是“更多接口名”,而是“请求生命周期并不只有一种形状”。

一张图:主生成链之外还有哪些常见分叉#

这张图解决的理解障碍是:很多读者脑中只有一条 GenerateReqInput -> Scheduler -> Detokenizer 主线,容易忽略非生成与控制请求的岔路。

flowchart TD
    In["HTTP / OpenAI / native request"] --> Gen["GenerateReqInput"]
    In --> Emb["EmbeddingReqInput / classify / score / rerank"]
    In --> Ctrl["session / abort / pause / flush / health"]
    Gen --> Main["generate path"]
    Emb --> Pool["embedding / classify / rerank path"]
    Ctrl --> Gate["control-plane path"]

图比纯文字多解释的一点是:这些分叉不是“额外功能”,而是共享同一入口之后沿不同运行时语义散开的主线分支。

embedding / classify / score / rerank 为什么不该按生成主线理解#

serving_embedding.pyserving_classify.pyserving_score.pyserving_rerank.py 看,这些 surface 往往会把请求转换成 EmbeddingReqInput,而不是 GenerateReqInput。这说明它们虽然共享 OpenAIServingBase.handle_request(...) 的入口骨架,但进入 runtime 时已经换成了不同对象模型。

这类分叉的阅读价值在于:同样是“从 OpenAI-compatible 表面进入系统”,有些请求追求的是文本生成,有些请求追求的是 embedding、分类分数、cross-encoder rerank 或 decoder-only rerank 结果。生命周期虽然共享入口,但中段和尾段语义并不相同。

rerank 路径为什么特别值得提#

serving_rerank.py 里显式写了两条可能:

  • cross-encoder reranker:适配成 EmbeddingReqInput pairs
  • 某些 decoder-only reranker:走不同解释路径

这说明 even within “非生成请求” 本身,也存在不同形状。把这层补进书里,能防止读者把所有非生成请求都简单等同于 embedding。

health_generate 为什么不是普通生成请求#

http_server.pyutils/common.py 里都有 /health_generate。这说明系统对健康检查的理解不是“随便发个普通请求看看能不能回”,而是提供了一条更接近运行时活性探测的专门路径。

结合前面写过的 watchdog / liveness 章节,这一点很重要:health_generate 的职责不是产出高价值业务结果,而是确认整个请求推进链是否仍然可达、可推进、可回包。

也正因为这样,它在 request lifecycle 里应被视为控制性探测请求,而不是普通业务生成请求。

open_session / close_session 怎么改变 request lifecycle 的边界#

http_server.py 里这两个路由直接落到 TokenizerManager.open_session(...) / close_session(...)。这说明 session 控制请求本身并不走完整 token generation loop,而是在 request lifecycle 的更外层修改“后续请求将以什么会话状态进入系统”。

换句话说:

  • 普通生成请求消耗当前 session 状态。
  • open_session / close_session 改写 session 边界本身。

这两种请求虽然都属于同一服务,但生命周期职责明显不同。

abort_requestpause_generation 为什么属于控制请求而不是错误处理补丁#

abort_requestpause_generationhttp_server.py 中都有专门入口。这说明系统把“主动中止请求”“暂停新生成流量”视为正式控制面,而不是异常情况下才临时触发的补丁逻辑。

这类设计对一本系统书尤其重要,因为它告诉读者:

  • abort 不是单纯客户端断开后的副作用。
  • pause 不是内部 debug hack。

它们都是 runtime 对自身调度与流量控制公开暴露出的能力。

这一层最容易出现的误判#

1. 把 embedding / classify / rerank 当成“生成的简化版”#

实际上它们进入 runtime 时就已经换了对象模型和输出语义。

2. 把 health_generate 当成普通业务请求#

它更接近 liveness 探测请求,不应与真实业务流量同口径理解。

3. 把 open_session / pause_generation 当成边缘管理接口#

它们直接修改的是后续请求进入主链的边界条件。

如果你在排查“为什么某类请求看起来不像生成主线”,先怎么查#

建议按这个顺序:

  1. 先确认当前是生成请求、embedding 类请求,还是控制请求。
  2. 看 serving handler 最终构造的是 GenerateReqInput 还是 EmbeddingReqInput
  3. 如果是控制请求,看它是否根本不会进入完整 scheduler/detokenizer 生成闭环。
  4. 再决定要回 request lifecycle 主线、运行时控制面,还是 observability/liveness 章节。

小结#

这一章真正想补齐的,是 request lifecycle 里经常被默认掉的事实:

  • 系统不只有一种请求形状。
  • 生成、非生成、探测、会话控制、暂停中止,共享入口但不共享同一条中段语义。
  • 只有把这些分叉看清,request lifecycle 才真正像一本书里的“请求全景图”,而不是单一路径说明。