Prhub

#49113 [Bugfix][Rust Frontend] Handle zero-column logprobs payloads without panicking

原始 PR 作者 FeathBow 合并时间 2026-07-21 12:30 文件变更 2 提交数 2 评论 5 代码增减 +59 / -0

执行摘要

修复 Rust 前端零列 logprobs 反序列化 panic

engine-core 协议可能携带零个评分位置的 logprobs 载荷,例如 [0,0][0,k+1] 张量形状,而解码器直接将列数传入 slice::chunks,当块大小为零时会无条件 panic。虽然 Python runner 不会发送此类形状,但 engine-core 协议是公开边界,panic 是错误的失败模式。

值得合并:修复明确,测试充分,风险低。但建议未来考虑对该解码路径增加 fuzz 测试以覆盖更多畸形载荷。

讨论亮点

审核者 BugenZhao 询问上下文是否来自 openinfer-project/openinfer#721,作者 FeathBow 澄清是在审查 openinfer-project/openinfer#722 时发现的,并非直接关联。最终 BugenZhao 以 LGTM 批准。

实现拆解

  1. rust/src/engine-core-client/src/protocol/logprobs.rsWireLogprobs::decode 方法中,在行/列形状校验之后、循环分段之前新增两个守卫分支:
    • token_ids.rows == 0,直接返回空的 Logprobs(兼容 [0,0][0,k+1] 两种编码)。
    • token_ids.cols == 0rows > 0,则通过 bail_ext_value_decode! 返回描述错误。
  2. rust/src/engine-core-client/src/protocol/logprobs/tests.rs 新增两个测试:
    • decodes_zero_row_logprobs_as_empty:验证形状 [0,0][0,3] 均解码为空。
    • rejects_zero_column_logprobs_with_rows:验证形状 [2,0] 返回 ExtValueDecode 错误而非 panic。
文件 模块 状态 重要度
rust/src/engine-core-client/src/protocol/logprobs.rs 协议解码 modified 6.2
rust/src/engine-core-client/src/protocol/logprobs/tests.rs 测试 modified 7.22

关键符号

decode

关键源码片段

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

核心修复逻辑所在:在 `decode` 方法中添加了零行和零列的前置检查,避免 panic。

// rust/src/engine-core-client/src/protocol/logprobs.rs// 在形状校验完成后,chunks 循环之前插入以下守卫分支// Empty position lists may be encoded as either [0, 0] or [0, k + 1].
// 零行载荷:直接返回空 positions,避免后续 `slice::chunks` 因 chunk size 为零而 panic
if token_ids.rows == 0 {
    return Ok(Logprobs {
        positions: Vec::new(),
    });
}// 零列但非零行的载荷:不可能有意义,报出结构化错误而非 panic
if token_ids.cols == 0 {
    bail_ext_value_decode!(
        "{field_prefix}: zero-column logprobs payload with {} rows",
        token_ids.rows
    );
}
rust/src/engine-core-client/src/protocol/logprobs/tests.rs test-coverage

新增了两个单元测试,覆盖零行和零列两种异常场景。

// rust/src/engine-core-client/src/protocol/logprobs/tests.rs#[test]
// 验证零行 logprobs 形状 [0,0] 和 [0,3] 均解码为空,不会 panic
fn decodes_zero_row_logprobs_as_empty() {
    for shape in [[0usize, 0], [0, 3]] {
        let frames = vec![Bytes::from(encode_value(&output_wire_with_custom_fields(
            None,
            Some(Value::Array(vec![
                ndarray_value("<i8", &shape, Value::Ext(3, Vec::new())),
                ndarray_value("<f4", &shape, Value::Ext(3, Vec::new())),
                ndarray_value("<i8", &[0], Value::Ext(3, Vec::new())),
                Value::Nil,
            ])),
        )))];
        let decoded = decode_engine_core_outputs(&frames)
            .unwrap()
            .into_request_batch()
            .unwrap();
        let logprobs = decoded.outputs[0]
            .new_prompt_logprobs_tensors
            .clone()
            .unwrap()
            .into_direct()
            .unwrap();
        assert!(logprobs.is_empty());
    }
}#[test]
// 验证零列但非零行的 payload 导致 ExtValueDecode 错误而非 panic
fn rejects_zero_column_logprobs_with_rows() {
    let ranks = Value::Ext(3, vec![1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]);
    let frames = vec![Bytes::from(encode_value(&output_wire_with_custom_fields(
        Some(Value::Array(vec![
            ndarray_value("<i8", &[2, 0], Value::Ext(3, Vec::new())),
            ndarray_value("<f4", &[2, 0], Value::Ext(3, Vec::new())),
            ndarray_value("<i8", &[2], ranks),
            Value::Nil,
        ])),
        None,
    )))];    let error = decode_engine_core_outputs(&frames).unwrap_err();
    let crate::error::Error::ExtValueDecode { message } = &error else {
        panic!("expected ExtValueDecode");
    };
    assert_eq!(
        message,
        "new_logprobs: zero-column logprobs payload with 2 rows"
    );
}

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

风险较低:变更为新增前置返回分支,不改变现有逻辑路径;仅影响 Rust engine-core-client 的 logprobs 解码路径;测试覆盖了新增的两种异常情况。但无单元测试以外的集成测试验证与其他引擎核心的兼容性。

直接影响自定义 engine-core 实现的用户,使他们不会因零行/零列 logprobs 载荷而遇到 panic;对使用原始 Python runner 的用户无影响(Python runner 不会发送这类载荷)。

缺少集成测试 影响自定义引擎核心

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论