Prhub

#45696 [Rust Frontend] Require `ModelConfig.vocab_size` to be present

原始 PR 作者 BugenZhao 合并时间 2026-06-16 13:30 文件变更 8 提交数 2 评论 4 代码增减 +69 / -126

执行摘要

使 Rust 前端强制要求 vocab_size 存在,移除冗余字段

PR 描述:Tighten Rust frontend vocab-size handling so sampling validation treats model vocab as a resolved model-config invariant, matching Python frontend behavior。同时清理无用的字段 max_position_embeddingsnum_attention_heads,它们曾用于前端推导 max_model_len,但现在引擎返回的值被直接使用。

建议审阅者重点关注复合模型的递归解析是否完善,以及移除 num_attention_heads 验证是否影响特殊模型。整体设计方向正确,通过使模型配置契约更严格提高了可靠性和可维护性,值得合并。

讨论亮点

Chatgpt-codex-connector 指出新 vocab_size() 只检查直接子 text_config,当复合模型嵌套多层时可能失败。第二次提交已改为递归调用,问题已解决。@njhill 批准了本 PR。

实现拆解

  1. 修改 ModelConfig 结构体:移除 max_position_embeddingsnum_attention_heads 字段。
  2. ModelConfig::vocab_size() 返回类型从 Option<u32> 改为 Result<u32>,当字段缺失时返回错误;同时递归检查嵌套 text_config
  3. 移除 validate_text_config_selection() 方法及其在 load_model_config() 中的调用,该检查由 vocab_size 必需性替代。
  4. 修改 SamplingLimitsmodel_vocab_sizeOption<usize> 改为 usize;删除 logprobs_vocab_size()stop_token_vocab_size() 方法。
  5. 修改 TextBackend trait:model_vocab_size() 返回 usize,默认返回 usize::MAX 以兼容测试后端。
  6. 更新 HfTextBackend:构造函数中调用 model_config.vocab_size() 并存储结果,model_vocab_size() 返回存储值。
  7. 调整 token_ids.rs 中的验证:直接使用 limits.model_vocab_size 替代间接方法。
  8. 更新测试代码:调整 SamplingLimits 构造中移除 Some 包装,删除处理未知模型词汇量的测试用例。
文件 模块 状态 重要度
rust/src/text/src/backend/hf/config.rs 文本层 modified 8.47
rust/src/text/src/backend/mod.rs 文本层 modified 7.67
rust/src/text/src/lower.rs 文本层 modified 7.18
rust/src/text/src/backend/hf/mod.rs 文本层 modified 6.07
rust/src/chat/src/lib.rs 对话层 modified 5.46
rust/src/text/src/lower/token_ids.rs 文本层 modified 5.85
rust/src/text/src/lib.rs 文本层 modified 5.01
rust/src/text/src/lower/logprobs.rs 文本层 modified 3.92

关键符号

ModelConfig::vocab_size SamplingLimits::model_vocab_size TextBackend::model_vocab_size HfTextBackend::model_vocab_size ChatLlm::model_vocab_size

关键源码片段

rust/src/text/src/backend/hf/config.rs core-logic

核心变更:`ModelConfig` 结构体移除字段,`vocab_size` 改为返回 `Result`,移除 `validate_text_config_selection`。

/// Model config representation.
/// Removed max_position_embeddings and num_attention_heads fields.
#[derive(Debug, Default, Deserialize)]
#[serde(default)]
pub struct ModelConfig {
    model_type: Option<String>,
    vocab_size: Option<u32>,
    num_experts: Option<OneOrManyExpertCount>,
    moe_num_experts: Option<OneOrManyExpertCount>,
    n_routed_experts: Option<OneOrManyExpertCount>,
    num_local_experts: Option<OneOrManyExpertCount>,
    block_configs: Vec<BlockConfig>,
    text_config: Option<Box<ModelConfig>>,
}impl ModelConfig {
    /// Return the effective model vocabulary size.
    /// Now returns `Result<u32>` instead of `Option<u32>`.
    /// Recursively checks nested text_config.
    pub fn vocab_size(&self) -> Result<u32> {
        if let Some(vocab_size) = self.vocab_size {
            Ok(vocab_size)
        } else if let Some(text_config) = self.text_config.as_deref() {
            // Recursively lookup nested text_config
            text_config.vocab_size()
        } else {
            Err(Error::Tokenizer(
                "the model config does not define `vocab_size`".to_string(),
            ))
        }
    }
}
rust/src/text/src/backend/mod.rs core-logic

定义了 `SamplingLimits` 和 `TextBackend` trait,核心类型变更。

/// Effective bounds used to validate and lower sampling requests.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SamplingLimits {
    pub max_model_len: u32,
    pub max_logprobs: i32,
    /// Model vocabulary size from the model config, used to bound generated
    /// token IDs and logits-domain sampling controls. Now always present.
    pub model_vocab_size: usize,
    pub tokenizer_vocab_size: usize,
}impl SamplingLimits {
    /// Return the union bound used to validate token-ID prompts.
    pub fn prompt_token_vocab_size(&self) -> usize {
        self.tokenizer_vocab_size.max(self.model_vocab_size)
    }
}pub trait TextBackend: Send + Sync {
    fn tokenizer(&self) -> DynTokenizer;
    fn is_moe(&self) -> bool { false }
    fn model_id(&self) -> &str;
    fn sampling_hints(&self) -> Result<SamplingHints> { Ok(SamplingHints::default()) }
    /// Return the model vocabulary size from the model config.
    /// The permissive default exists for lightweight test backends.
    fn model_vocab_size(&self) -> usize { usize::MAX }
    fn tokenizer_vocab_size(&self) -> usize { self.tokenizer().vocab_size() }
}

评论区精华

递归 text_config 词汇表查找 正确性

Chatgpt-codex-connector 评论指出,新的 `vocab_size()` 实现只检查直接子 `text_config`,当复合模型嵌套多层 `text_config` 时可能失败。建议保留递归查找。

结论:第二次提交 `recursively lookup vocab_size` 已实现递归调用,问题解决。 · 已解决

风险与影响

  • 核心路径变更:ModelConfig::vocab_size 现在要求必有值,缺少 vocab_size 的模型在 Rust 前端启动时失败,与 Python 前端行为一致。
  • 移除 num_attention_heads 验证:之前用于拒绝无效 text_config,现在由 vocab_size 必需性替代,但可能改变某些复合模型的验证行为。
  • 默认 model_vocab_size 返回 usize::MAX:测试后端使用该默认值,可能掩盖 token ID 范围错误,但生产后端 HfTextBackend 会覆盖。
  • 影响范围:仅限于 Rust 前端(rust/textrust/chatrust/server),Python 前端不受影响。
  • 对用户:如果使用 Rust 前端且模型配置缺少 vocab_size,将收到明确的初始化错误。标准 Hugging Face 模型不受影响。
  • 对系统:提高了与 Python 前端的行为一致性,简化了词汇量边界处理,消除了潜在的回退逻辑。
核心路径变更 移除 num_attention_heads 验证 默认 model_vocab_size 为 usize::MAX 缺少 vocab_size 的模型启动失败

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论