Prhub

#46348 [Rust Frontend] Align Rust allowed_token_ids validation with Python

原始 PR 作者 reidliu41 合并时间 2026-06-23 16:32 文件变更 6 提交数 3 评论 5 代码增减 +138 / -27

执行摘要

Rust 前端拒绝空 allowed_token_ids,对齐 Python 校验

PR body 指出 Python 端 SamplingParams 会拒绝 allowed_token_ids is not None and empty!,而 Rust 前端当前允许空列表通过。为了对齐行为并避免潜在的静默错误,需要添加相应校验。

此 PR 是典型的跨语言行为对齐修复,代码简洁、构造清晰、测试完备,适合浏览以了解 Rust 前端的参数验证流程。特别值得注意的是错误类型的重构思路——通过枚举替代独立结构体,保持了错误层次的整洁。

讨论亮点

BugenZhao 在 review 中提出了两点建议:

  1. 将独立的 OutOfVocabError 结构体内联为 TokenIdsError 枚举的一个变体,避免 text::Error 中出现多个变体,使得错误类型层次更清晰。该建议已被采纳。
  2. 错误消息格式从 "allowed_token_ids is not None and empty!" 改为 "allowed_token_ids should not be empty",更加简洁且与 Python 错误风格一致。该建议也已被采纳。

实现拆解

  1. 引入新的错误枚举:在 rust/src/text/src/lower/token_ids.rs 中,将原有的 OutOfVocabError 结构体重构为 TokenIdsError 枚举,新增 EmptyAllowedTokenIds 变体。
  2. 添加空列表检查:在 validate_vocab_range 函数中,当 allowed_token_ids 存在且为空时,立即返回 EmptyAllowedTokenIds 错误。
  3. 更新导出和测试匹配:在 rust/src/text/src/error.rsrust/src/text/src/lower.rs 中将所有 OutOfVocabError 引用替换为 TokenIdsError::OutOfVocab,并在 lower.rs 中新增单元测试 lower_sampling_params_rejects_empty_allowed_token_ids
  4. 完善错误映射:在 rust/src/server/src/error.rs 中将 is_request_validation_error 匹配的 OutOfVocab 变体更新为 TokenIds,确保新错误正确映射到 HTTP 400;同时新增 empty_allowed_token_ids_maps_to_invalid_request 单元测试。
  5. 集成测试验证:在 rust/src/server/src/routes/tests.rs 中新增两个集成测试,分别验证 /v1/completions/v1/chat/completions 端点传入空 allowed_token_ids 时返回 400 错误并包含正确错误消息。
文件 模块 状态 重要度
rust/src/text/src/lower/token_ids.rs 令牌验证 modified 6.67
rust/src/text/src/lower.rs 参数降层 modified 6.28
rust/src/server/src/error.rs 错误映射 modified 5.28
rust/src/server/src/routes/tests.rs 路由测试 modified 7.15
rust/src/text/src/error.rs 错误类型 modified 4.1
rust/src/text/src/lib.rs 库导出 modified 3.83

关键符号

validate_vocab_range validate_param is_request_validation_error lower_sampling_params_rejects_empty_allowed_token_ids chat_completions_empty_allowed_token_ids_returns_openai_error completions_empty_allowed_token_ids_returns_openai_error empty_allowed_token_ids_maps_to_invalid_request

关键源码片段

rust/src/text/src/lower/token_ids.rs core-logic

核心验证逻辑变更:引入 TokenIdsError 枚举并添加空列表检查。

// 文件 : rust/src/text/src/lower/token_ids.rs
// 引入的 TokenIdsError 枚举,统一 token ID 验证错误
#[derive(Debug, Error)]
pub enum TokenIdsError {
    // 新增:allowed_token_ids 为空时使用此变体
    #[error("`allowed_token_ids` should not be empty")]
    EmptyAllowedTokenIds,
    // 原有 out-of-vocab 错误内联为枚举变体
    #[error(
        "token_id(s) {token_ids:?} in {parameter} contain out-of-vocab token ids. \
         Vocabulary size: {vocab_size}"
    )]
    OutOfVocab {
        parameter: &'static str,
        token_ids: Vec<u32>,
        vocab_size: usize,
    },
}// validate_vocab_range 函数:校验采样参数中的 token ID 范围
pub(crate) fn validate_vocab_range(
    params: &EngineCoreSamplingParams,
    limits: &SamplingLimits,
) -> Result<(), TokenIdsError> {
    // 校验 stop_token_ids
    validate_param("stop_token_ids", params.stop_token_ids.iter().copied(), limits.model_vocab_size)?;    // 校验 allowed_token_ids:先检查是否为空,再校验范围
    if let Some(token_ids) = params.allowed_token_ids.as_deref() {
        // 新增空列表检查:Python 端同样拒绝此情况
        if token_ids.is_empty() {
            return Err(TokenIdsError::EmptyAllowedTokenIds);
        }
        validate_param("allowed_token_ids", token_ids.iter().copied(), limits.tokenizer_vocab_size)?;
    }    // 其余 logit_bias、logprob_token_ids、bad_words_token_ids 校验保持不变
    // ...
    Ok(())
}
rust/src/server/src/error.rs core-logic

更新错误映射和测试,确保新错误类型映射到 HTTP 400。

// 文件 : rust/src/server/src/error.rs
// 判断是否为请求验证错误的函数
fn is_request_validation_error(error: &vllm_text::Error) -> bool {
    matches!(
        error,
        vllm_text::Error::PromptTooLong { .. }
            | vllm_text::Error::EmptyPromptTokenIds { .. }
            | vllm_text::Error::Logprobs(_)
            // 将旧的 OutOfVocab 变体替换为新的 TokenIds 变体
            | vllm_text::Error::TokenIds(_)
            | vllm_text::Error::InvalidThinkingTokenBudget
            | vllm_text::Error::Llm(vllm_llm::Error::EmptyPromptTokenIds { .. })
    )
}// 新增测试:验证空 allowed_token_ids 映射为 400 invalid_request_error
#[test]
fn empty_allowed_token_ids_maps_to_invalid_request() {
    let error = vllm_text::Error::TokenIds(vllm_text::TokenIdsError::EmptyAllowedTokenIds);
    let api_error = text_submit_error("failed to submit completion request", error);
    assert_eq!(api_error.status_code(), StatusCode::BAD_REQUEST);
    let response = api_error.to_error_response();
    assert_eq!(response.error.error_type, "invalid_request_error");
    assert!(response.error.message.contains("allowed_token_ids"));
}

评论区精华

错误类型重构:将 OutOfVocabError 内联为 TokenIdsError 变体 设计

BugenZhao 建议将独立的 OutOfVocabError 结构体内联进新的 TokenIdsError 枚举中,避免 text::Error 引入多个变体,保持错误类型层次清晰。

结论:采纳建议,最终 commit 中 OutOfVocabError 成为 TokenIdsError::OutOfVocab 变体。 · 已解决

错误消息格式改进 style

BugenZhao 建议将错误消息从 "allowed_token_ids is not None and empty!" 改为 "`allowed_token_ids` should not be empty",以符合 Rust 前端惯用格式。

结论:采纳建议,最终错误消息使用新格式。 · 已解决

风险与影响

此变更直接影响请求验证早期阶段:任何显式传入空 allowed_token_ids 的请求现都会被拒绝并返回 400 错误。对于之前依赖此非法行为的客户端会造成 breakage,但这正是修复目的所在。风险较低,且已有单元测试和集成测试覆盖。需要关注的是,如果下游有代码通过设置空列表来“禁用”该参数(实际不应如此),需要及时适配。未引入性能或安全问题。

对用户:使用 allowed_token_ids: [] 的请求将收到明确的 400 错误,而非被静默接受。对系统:前后端行为一致,减少维护和排查负担。对团队:此 PR 健全了 Rust 前端的参数校验体系,为后续其他参数的对齐提供了参考模式。影响范围限于请求入口,不涉及核心采样或引擎层。

向后兼容性变更 验证路径调整

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论