Prhub

#37226 [Rust] Simplify request defaults and document batch header ABI

原始 PR 作者 merrymercy 合并时间 2026-09-01 03:31 文件变更 2 提交数 21 评论 4 代码增减 +49 / -59

执行摘要

精简 GenerateBody 反序列化注解并文档化 batch header ABI

该 PR 是 rust-server-cleanup 栈的第 4 步,目的是去除冗余反序列化注解、并把紧凑 batch header 的契约放在其定义旁边。PR body 明确说明为“Remove redundant request deserialization annotations and put the compact batch-header contract next to its definition”,并强调“No runtime or wire-format behavior is intended to change”。通过把 GenerateBody 中 Option 字段上的 serde(default) 删除,让类型本身表达缺省语义;同时修正 BatchHeader 文档中“四个核心列必填、仅 extras 后缀默认空”的准确契约,并把含混的 tid 头字段族重命名为语义更准确的 tokids_lp(对应 Python 侧 token_ids_logprob),避免未来维护者误读线上 ABI。

值得快速阅读,重点看两处:请求体 Option 字段删除 serde(default) 的清理模式,以及 BatchHeader 的 direction_family_shape 命名契约文档。如果后续要改 batch header 或新增 extras 列,应严格遵循该命名语法并保持与 Python header_cols 顺序一致。

讨论亮点

review 讨论较少:merrymercy 自己提交了 COMMENTED 状态的 approve,rainj-me 给出 APPROVED 空评论。Issue 评论中主要是 /rerun-test 机器人与 rerun 结果确认。未出现关于重命名、serde 默认值或 ABI 文档的实质性质疑,设计决策(tid → tokids_lp、Option 字段删除 serde(default))在 PR body 中已说明并获批。

实现拆解

实现按以下步骤拆解:

  1. 精简 GenerateBody 反序列化注解(rust/sglang-server/src/message/request.rs):删除 rid、text、input_ids、sampling_params、return_logprob、logprob_start_len、top_logprobs_num、token_ids_logprob、return_hidden_states、return_text_in_logprobs、bootstrap_host、bootstrap_port、bootstrap_room、bootstrap_pair_key、decode_tp_size、routed_dp_rank、disagg_prefill_dp_rank、image_data、mm_hashes、video_data、audio_data 等所有 Option 字段上的 #[serde(default)];仅保留 stream: bool 上的注解。原因:Option 缺省时 serde 自动反序列化为 None,注解是冗余噪音;该改动纯源码层面清理,不改变任何请求解析行为。

  2. 修正并扩充 BatchHeader 文档(rust/sglang-server/src/message/response.rs):将旧的“所有数值字段都 serde(default),热路径只发前四个”改为“前四个字段必填,tok_lens 之后的每个字段默认空,因此热路径只发四元素 header”,并明确字段顺序即线上 ABI,必须与 python/sglang/srt/rust_server/server.py 中 RustTokenizerManager.push_generation 的 header_cols 保持一致。

  3. 新增方向/族/形状命名语法文档:BatchHeader 字段名遵循 direction_family_shape 语法——direction 分为 out(decode 输出)与 in(prefill 输入);family 分为 lp(token logprobs)、top(top-k logprobs)、tokids_lp(请求指定 token 的 logprobs)与 hidden(隐藏状态);shape 分为 lens(每请求元素数)、reqlens(每请求位置/行数)与 poslens(每位置/行元素数)。

  4. 重命名 tid 字段族为 tokids_lp:将 out_tid_reqlens、out_tid_poslens、in_tid_reqlens、in_tid_poslens 分别改为 out_tokids_lp_reqlens、out_tokids_lp_poslens、in_tokids_lp_reqlens、in_tokids_lp_poslens,同步更新 for_each_chunk 中 per_req_ok、sum 对照检查、n_od/n_id 列元素计数、has_extras 守卫以及 take_ragged 读取处的全部引用,并更新测试内 header 数组的注释。字段顺序和线上布局不变(serde 按声明顺序序列化结构体字段),因此纯重命名。

  5. 配套测试与构建验证:PR body 给出 cargo test(251 passed)、cargo clippy -D warnings、cargo fmt --check 三项验证结果,并通过 /rerun-test 触发 rust 测试、srt_endpoint、rust-native mm e2e 与 openai completion rust 测试,全部通过。本次没有新增独立测试文件,测试覆盖依赖 response.rs 内已有的单元测试模块。

文件 模块 状态 重要度
rust/sglang-server/src/message/response.rs Rust 服务 modified 6.44
rust/sglang-server/src/message/request.rs Rust 服务 modified 5.69

关键符号

GenerateBody::into_requests frame_decode_batch_cols for_each_chunk take_poslens take_flat take_i32

关键源码片段

rust/sglang-server/src/message/response.rs core-logic

BatchHeader 文档化 ABI 契约、字段重命名 tid→tokids_lp 并同步 for_each_chunk 解码校验与测试注释,是理解 batch header wire 格式的关键文件。

/// Columnar scalar header for a whole decode batch. The first four fields are
/// required; every field after `tok_lens` defaults empty, so the hot path emits
/// a four-element header. Field order is the wire ABI and must match
/// `RustTokenizerManager.push_generation`'s `header_cols` in
/// `python/sglang/srt/rust_server/server.py`.
///
/// Field names follow `direction_family_shape`:
/// - direction: `out` = decode output, `in` = prefill input;
/// - family: `lp` = token logprobs, `top` = top-k logprobs, `tokids_lp` =
///   requested-token logprobs, and `hidden` = hidden states;
/// - shape: `lens` counts elements per request, `reqlens` counts positions or
///   rows per request, and `poslens` counts elements per position or row.
///
/// 关键点:字段顺序即线上 ABI,msgpack 按声明顺序输出;前四列(rids、
/// finish_reasons、prompt_tokens、tok_lens)必填,其后每个列都有
/// #[serde(default)],缺省为空数组,热路径因此只发四元素 header。
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct BatchHeader {
    /// Request ids, as the same strings Python holds (`Req.rid`, uuid hex) —
    /// hashed back to the internal routing key in `decode_one`
    /// (`Rid::shard`), mirroring the control path. The wire has no
    /// rid-shape coupling; any string is a valid rid.
    pub rids: Vec<String>,
    pub finish_reasons: Vec<Option<FinishReason>>,
    pub prompt_tokens: Vec<u32>,
    pub tok_lens: Vec<u32>,    // 以下 extras 全部 `#[serde(default)]`:不存在整列时为空数组,
    // `has_extras` 只检查 reqlens 族即可跳过整套 extras 解码。
    #[serde(default)]
    pub out_lp_lens: Vec<u32>,
    #[serde(default)]
    pub in_lp_lens: Vec<u32>,
    #[serde(default)]
    pub out_top_reqlens: Vec<u32>,
    #[serde(default)]
    pub out_top_poslens: Vec<u32>,
    #[serde(default)]
    pub in_top_reqlens: Vec<u32>,
    #[serde(default)]
    pub in_top_poslens: Vec<u32>,
    // tid 族重命名为 tokids_lp,语义对齐 Python 的 token_ids_logprob,
    // 字段顺序不变,wire 格式不变。
    #[serde(default)]
    pub out_tokids_lp_reqlens: Vec<u32>,
    #[serde(default)]
    pub out_tokids_lp_poslens: Vec<u32>,
    #[serde(default)]
    pub in_tokids_lp_reqlens: Vec<u32>,
    #[serde(default)]
    pub in_tokids_lp_poslens: Vec<u32>,
    #[serde(default)]
    pub hidden_reqlens: Vec<u32>,
    #[serde(default)]
    pub hidden_poslens: Vec<u32>,
}
rust/sglang-server/src/message/request.rs core-logic

删除 GenerateBody 所有 Option 字段上冗余的 serde(default) 注解,清理反序列化配置噪音,体现 Option 类型自身语义。

/// The `/generate` wire body before batch splitting: `text`/`input_ids`/
/// `sampling_params` each scalar-or-list, fanned into per-request
/// [`GenerateRequest`]s by [`into_requests`](GenerateBody::into_requests).
///
/// Unknown keys are IGNORED, matching Python: FastAPI builds `GenerateReqInput`
/// as a pydantic dataclass, which drops extras. `deny_unknown_fields` here
/// turned every `GenerateReqInput` field this server has not ported —
/// `priority`, `extra_key`, `session_id`, `session_params`,
/// `return_sampling_mask`, `custom_logit_processor`, and ~40 more — into a
/// 400, so a client that worked against the Python server broke against this
/// one. The cost of dropping it is that a typo (`temperature`) is silently
/// ignored rather than reported; that is the same trade Python already makes.
///
/// 清理说明:Option 字段缺省时 serde 自动反序列化为 None,无需重复标注
/// #[serde(default)];仅非可选字段(stream)需要显式默认值。
#[derive(Debug, Clone, Default, Deserialize)]
pub struct GenerateBody {
    /// Optional client-supplied request id(s): a single string (a batch fans it
    /// out as `{rid}_{i}`, mirroring Python `_normalize_batch`) or one per item.
    pub rid: Option<OneOrMany<String>>,
    pub text: Option<OneOrMany<String>>,
    pub input_ids: Option<OneOrMany<TokenIds>>,
    #[serde(default)]
    pub stream: bool,
    /// One params object (broadcast) or a list of them (per item); see
    /// [`SamplingParamsInput`].
    pub sampling_params: Option<SamplingParamsInput>,
    /// Logprob / hidden-state options: a scalar broadcasts to every prompt, a
    /// list is per-prompt (Python `_normalize_logprob_params`).
    pub return_logprob: Option<OneOrMany<bool>>,
    pub logprob_start_len: Option<OneOrMany<i64>>,
    pub top_logprobs_num: Option<OneOrMany<i64>>,
    /// Token ids to report logprobs for: one list (broadcast to every prompt) or
    /// one list per prompt, mirroring Python's
    /// `Union[List[int], List[List[int]]]` fan-out in `_normalize_batch`.
    pub token_ids_logprob: Option<OneOrMany<TokenIds>>,
    pub return_hidden_states: Option<OneOrMany<bool>>,
    /// Scalar-only in Python too (`return_text_in_logprobs: bool`).
    pub return_text_in_logprobs: Option<bool>,
    // PD-disaggregation routing, injected per request by the PD router
    // (mini_lb / sgl-model-gateway): a scalar for a single prompt, one-per-item
    // lists for a batch. Elements are nullable (`List[Optional[...]]` in
    // Python) — the router sends `bootstrap_port: [null, …]` when deferring to
    // the scheduler's `--disaggregation-bootstrap-port` default.
    pub bootstrap_host: Option<OneOrMany<Option<String>>>,
    pub bootstrap_port: Option<OneOrMany<Option<i64>>>,
    /// `bootstrap_room` fits in i64: the PD routers draw it from `[0, 2^63)`.
    pub bootstrap_room: Option<OneOrMany<Option<i64>>>,
    pub bootstrap_pair_key: Option<OneOrMany<Option<String>>>,
    pub decode_tp_size: Option<OneOrMany<Option<i64>>>,
    /// DP routing hints — per-request scalars even for batches, as in Python.
    pub routed_dp_rank: Option<i64>,
    pub disagg_prefill_dp_rank: Option<i64>,
    // Multimodal inputs, permissive `Value` so any shape Python's
    // `GenerateReqInput` accepts (URL / base64 / list / list-of-lists) parses.
    // `into_requests` fans them out per the Python
    // `_normalize_{image,video,audio}_data` batch rules.
    pub image_data: Option<rmpv::Value>,
    /// Caller-supplied per-item content hashes (hex) overriding the computed
    /// ones, so an external router's keys align with the prefix cache. Single
    /// requests only: Python declares the batched shapes but `__getitem__` never
    /// forwards them, so a batch is rejected here rather than answered with
    /// hashes it did not ask for.
    pub mm_hashes: Option<rmpv::Value>,
    pub video_data: Option<rmpv::Value>,
    pub audio_data: Option<rmpv::Value>,
}

评论区精华

重命名 tid 字段族为 tokids_lp 的语义与 ABI 影响 设计

PR body 说明将含混的 tid header 族重命名为 tokids_lp,不改变字段顺序或线上布局;对应 Python 侧 token_ids_logprob 语义,避免未来维护者误读字段含义。

结论:纯重命名,wire 格式不变,字段语义与 Python token_ids_logprob 对齐。 · 已解决

Option 字段删除 serde(default) 的行为等价性 question

PR body 说明删除 GenerateBody 所有 Option 字段上的 serde(default) 注解,仅保留 stream 非可选字段的注解;Option 缺省时 serde 自动反序列化为 None,因此行为不变。

结论:删除冗余注解,解析行为不变;cargo test 251 passed 验证。 · 已解决

风险与影响

技术风险:

  1. 重命名字段仅在 Rust 结构体层面进行,serde 序列化按字段声明顺序输出,字段顺序未变,因此 wire 格式保持不变;但若未来有人按字段名(而非顺序)手工构造 BatchHeader 或 msgpack,可能因字段名不同而漏改,属于低概率维护性风险。
  2. 删除 Option 字段上的 serde(default) 依赖 serde 对 Option 的隐式缺省行为,行为等价,无回归风险。
  3. 文档声称字段顺序与 Python 侧 header_cols 必须一致,若 Python 侧后续调整 header_cols 顺序而 Rust 侧未同步,会产生难以排查的跨语言 ABI 错位;本 PR 只新增了文档约束,没有增加编译期或运行期校验。
  4. 该 PR 是 21 个 commit 合并后的结果,包含多次 merge main,但最终 diff 仅涉及两个文件,冲突风险已在合并中消化。

影响范围:

  1. 用户/客户端:无感知。GenerateBody 解析行为不变,BatchHeader wire 格式不变,纯重命名与注释更新。
  2. 系统/运行时:无运行时行为变化;文档明确了 batch header ABI 契约,降低后续维护者跨语言(Rust server + Python producer)改动的出错概率。
  3. 团队/协作:作为 rust-server-cleanup 4/4 的收尾 PR,为后续 Rust server 结构调整提供了命名与文档基线;字段命名与 Python 侧 token_ids_logprob 语义对齐,减少跨语言概念映射成本。
跨语言 ABI 仅文档约束,无编译期校验 重命名后字段名散落测试注释,易漏改 21 commits 合并后仅 2 文件 diff,合并噪音大

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论