Prhub

#45848 [Rust Frontend] Add serde defaults for omit_defaults fields in `EngineCoreSamplingParams`

原始 PR 作者 wseaton 合并时间 2026-06-17 14:40 文件变更 3 提交数 1 评论 2 代码增减 +121 / -13

执行摘要

修复 Rust 反序列化 omit_defaults 字段缺失问题

Python 的 sampling_params 是 msgspec.Struct(omit_defaults=True),所以 engine-core 请求中 sampling_params 的键值对是稀疏的(省略了全部默认字段)。但 Rust 的 EngineCoreSamplingParams 只有 temperature 等部分字段有 #[serde(default)],导致解码时 msgpack 报 missing field temperature。该问题由作者在为监控 engine-core ZMQ socket 构建 trace 记录时发现,DiffusionGemma 请求因解码失败未被记录。

该 PR 彻底解决了 Rust 端与 Python 端序列化默认值不匹配的问题。建议阅读以了解如何正确处理 omit_defaults 的跨语言序列化。审核者指出正常环境可能不会触发此问题,但修复增强了健壮性。

讨论亮点

审核者 BugenZhao 评论:'IIUC, this issue only occurs when we're going to deserialize EngineCoreSamplingParams on the Rust side, which won't be encountered in normal frontend usage. But it's still great to have.' 表明此问题仅在 Rust 反序列化场景中出现,正常前端使用不会遇到,但修复仍有价值。

实现拆解

  1. 添加默认值函数:在 mod.rs 中新增 default_temperature()(返回 1.0)和 default_max_tokens()(返回 16),对应 Python 端的非零默认值。
  2. 容器级默认值:为 EngineCoreSamplingParams 结构体增加 #[serde(default)] 属性和 DefaultFromSerde derive,使所有字段在解码时若缺失则自动使用默认值。同时移除部分字段上不再需要的 #[serde(default)] 属性(如 top_kmin_tokensmin_plogit_bias 等),简化代码。
  3. Python 兼容测试更新python_compat.py 中将 EngineCoreSamplingParams 镜像 struct 改为 omit_defaults=True,使其编码行为与生产代码一致;修正 max_tokens 默认值为 16(原为 65536);新增全默认值请求 defaults_request 以验证空 map 解码。
  4. Rust 测试增强:在 python_msgpack_fixtures_match_rust_encoding 测试中读取 defaults_request 的 msgpack 数据,反序列化为 EngineCoreRequest 并断言其 sampling_params 字段均为 Python 默认值,确保回归防护。
文件 模块 状态 重要度
rust/src/engine-core-client/src/protocol/mod.rs 协议层 modified 7.96
rust/src/engine-core-client/src/tests/python_compat.py 兼容测试 modified 5.43
rust/src/engine-core-client/src/tests/client.rs 客户端测试 modified 5.31

关键符号

default_temperature default_max_tokens

关键源码片段

rust/src/engine-core-client/src/protocol/mod.rs core-logic

核心变更文件,添加 serde 默认值支持,修复解码缺失字段问题

/// 默认温度值,匹配 Python 端的 float 1.0
fn default_temperature() -> f32 {
    1.0
}/// 默认最大生成 token 数,匹配 Python 端的 int 16
fn default_max_tokens() -> u32 {
    16
}// Python 的 SamplingParams 是 `omit_defaults=True`,所以 msgpack 会省略
// 默认值字段;为整个结构体应用 `#[serde(default)]`。
// 针对非零默认值的字段,利用 `default = "..."` 属性指定默认函数。
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, DefaultFromSerde)]
#[serde(default)]
pub struct EngineCoreSamplingParams {
    /// 控制随机性,0 表示贪心采样
    #[serde(default = "default_temperature")]
    pub temperature: f32,    /// 累计概率阈值,默认 1.0
    #[serde(default = "default_top_p")]
    pub top_p: f32,    /// 考虑的前 top-k 个 token,0 表示所有 token
    pub top_k: u32,    /// 随机种子
    pub seed: Option<i64>,    /// 最大生成 token 数,默认 16
    #[serde(default = "default_max_tokens")]
    pub max_tokens: u32,    // 其余字段省略,均通过容器级默认值处理
}
rust/src/engine-core-client/src/tests/python_compat.py test-coverage

Python 测试脚本,更新镜像结构与生产代码一致,新增全默认值请求用例

# 镜像了实际的 SamplingParams;omit_defaults 使 fixture 匹配真实映射。
class EngineCoreSamplingParams(msgspec.Struct, dict=True, omit_defaults=True):
    temperature: float = 1.0
    top_p: float = 1.0
    top_k: int = 0
    seed: int | None = None
    max_tokens: int = 16 # 修正为与 Python 默认一致
    min_tokens: int = 0
    min_p: float = 0.0
    frequency_penalty: float = 0.0
    presence_penalty: float = 0.0
    repetition_penalty: float = 1.0
    stop_token_ids: list[int] = []
    _eos_token_id: int | None = None
    _all_stop_token_ids: set[int] = set()
    output_kind: RequestOutputKind = RequestOutputKind.DELTA# 全默认值 -> 空 map。回归防护:确保稀疏映射可解码。
defaults_request = EngineCoreRequest(
    request_id="req-defaults",
    prompt_token_ids=[5, 6, 7],
    mm_features=None,
    sampling_params=EngineCoreSamplingParams(),
    pooling_params=None,
    arrival_time=1.0,
)

评论区精华

反序列化场景影响范围 question

审核者 BugenZhao 评论:'IIUC, this issue only occurs when we're going to deserialize EngineCoreSamplingParams on the Rust side, which won't be encountered in normal frontend usage. But it's still great to have.'

结论:该问题已修复并合并,审核者认为正常前端使用不会触发,但修复提升了健壮性。 · 已解决

风险与影响

风险较低。改动仅限于 Rust 端解码逻辑,编码行为不变(未使用 skip_serializing_if),Python 端不受影响。测试覆盖了全默认值请求和现有兼容性测试。但由于涉及跨语言 msgpack 序列化协议,未来若 Python 端添加具有非零默认值的新字段,需同步更新 Rust 端的默认值函数,否则可能再次出现 missing field 错误。

直接修复 Rust 前端解码 engine-core 请求时的崩溃问题,影响使用 Rust 前端的场景(如 vllm-rust-serving)。Python 前端不受影响。对系统性能无影响。

跨语言协议兼容性 解码路径变更 新字段默认值同步风险

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论