Prhub

#49604 [Rust Frontend] Add --limit-mm-per-prompt support

原始 PR 作者 cinnamonica02 合并时间 2026-07-29 17:21 文件变更 14 提交数 1 评论 14 代码增减 +413 / -16

执行摘要

Rust 前端新增 --limit-mm-per-prompt 限制多模态输入数量

为了与 Python 前端的行为对齐,Rust 前端需要支持 --limit-mm-per-prompt,以限制多模态输入数量,防止滥用或超过模型支持上限。Python 已有类似实现(vllm/config/multimodal.py 中的 limit_per_promptvalidate_num_items),Rust 前端需要补充这个能力。

该 PR 是 Rust 前端功能对齐的重要里程碑,设计上考虑了与 Python 的兼容性、类型安全和可扩展性。评审讨论深入,值得团队精读,尤其是数据类型的序列化策略和跨 crate 数据传递模式。

讨论亮点

评论区精华

  • 默认值设计(@BugenZhao):"I feel a limit of 999 doesn't make too much sense... Shall we default to unlimited instead?" 采纳,改为缺席模态无限制。
  • 可配置形式支持(@cinnamonica02 回应 P2 评论):当前仅接受纯计数形式,但通过 extra 字段保留并转发引擎选项(如 num_frames),后续计划跟进对象形式。
  • 模态合并(@BugenZhao 询问 ImageEmbeds 是否独立模态):确认后修正,将 ImageEmbeds 映射到 Image 限制计数,与 Python 行为一致。
  • 托管引擎转发(@BugenZhao 确认参数是否被引擎读取):确认 Python 端通过 EngineArgs.add_cli_args 支持 --limit-mm-per-prompt,JSON 序列化后可直接传递。
  • 类型安全(@BugenZhao 建议使用封闭枚举 MmLimitModality 而非泛型 HashMap<String, usize>):作者采用,将类型定义在 chat crate 中并通过 serde 反序列化。

实现拆解

实现拆解

  1. 定义数据类型:在 rust/src/chat/src/multimodal.rs 中新增 MmLimitModality(封闭枚举,包含 ImageAudioVideo)、MmLimitSpec(可接受纯计数或带额外选项的对象)和 MmLimitPerPrompt(映射类型)。使用 serde 反序列化 JSON,并允许未知字段转发 Python 引擎的配置选项。

  2. 注入 CLI 参数:在 rust/src/cmd/src/cli.rsSharedRuntimeArgs 中添加 --limit-mm-per-prompt 字段,通过 parse_json<MmLimitPerPrompt> 解析 JSON 输入。提供 limit_mm_per_prompt_json 方法返回序列化后的 JSON 字符串用于转发。

  3. 传递到多模态模型信息:修改 MultimodalModelInfo::from_paths 签名,接收 MmLimitPerPrompt 参数,并存储为内部字段。构造时通过多个模块(backend, config 等)将限制传递到顶层入口。

  4. 请求验证:在 rust/src/chat/src/multimodal.rs 中实现 validate_mm_limits 函数,在 fetch_media 之前检查每个请求中每个模态的 item 数量是否超过配置限制。若超限则返回 HTTP 400 错误,错误信息与 Python 端保持一致。

  5. 托管引擎转发:在 rust/src/managed-engine/src/cli.rsManagedEngineArgs::into_config 中,当限制非空时,将 JSON 字符串以 --limit-mm-per-prompt 参数传递给 Python 引擎子进程,确保托管模式下双重验证(Rust 前端先拒绝,引擎也做检查)。

  6. 测试覆盖:在 rust/src/cmd/src/cli/tests.rs 增加 serve_args_reject_unsupported_modality_in_limit_mm_per_prompt 测试,确保解析器拒绝不支持的模态键。在 rust/src/server/src/routes/tests.rs 增加集成测试 non_stream_chat_rejects_when_image_count_exceeds_limit_mm_per_prompt,模拟两图请求在 limit=1 时被拒绝返回 400。

  7. 配套更新:更新示例文件 rust/src/server/examples/external_engine_openai_qwen.rs 以包含新字段,调整序列化逻辑以确保 count 字段可选。将失效的 unsupported.rs 中的过期配置移除。

文件 模块 状态 重要度
rust/src/chat/src/multimodal.rs 多模态层 modified 8.84
rust/src/server/src/routes/tests.rs 路由测试 modified 7.49
rust/src/cmd/src/cli.rs CLI 参数 modified 6.92
rust/src/cmd/src/cli/tests.rs CLI 测试 modified 6.59
rust/src/managed-engine/src/cli.rs 托管引擎 modified 5.51

关键符号

as_str count media_part_limit_modality validate_mm_limits limit_mm_per_prompt_json qwen_multimodal_model_info_with_limits non_stream_chat_rejects_when_image_count_exceeds_limit_mm_per_prompt serve_args_reject_unsupported_modality_in_limit_mm_per_prompt

关键源码片段

rust/src/chat/src/multimodal.rs core-logic

核心变更文件,定义了多模态限制的数据结构、序列化和验证逻辑。

/// Per-modality item-count limits configured by `--limit-mm-per-prompt`.
///
/// Modalities absent from the map are unlimited.
pub type MmLimitPerPrompt = HashMap<MmLimitModality, MmLimitSpec>;/// Modalities that `--limit-mm-per-prompt` can be keyed by.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MmLimitModality {
    Image,
    Audio,
    Video,
}impl MmLimitModality {
    /// The wire name, matching Python's modality strings.
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Image => "image",
            Self::Audio => "audio",
            Self::Video => "video",
        }
    }
}/// One modality's limit, in either of the two shapes Python accepts.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(
    untagged,
    expecting = "an item count, or an object with an optional `count` field"
)]
pub enum MmLimitSpec {
    /// Legacy form: `"image": 16`
    Count(usize),
    /// Configurable form: `"video": {"count": 1, "num_frames": 32}`
    Options {
        /// Absent means unlimited, matching an absent modality.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        count: Option<usize>,
        /// Preserve Python-owned options for forwarding. Never interpreted
        /// here: they size the engine's dummy-profiling encoder cache, which
        /// has no Rust counterpart.
        #[serde(flatten)]
        extra: BTreeMap<String, serde_json::Value>,
    },
}impl MmLimitSpec {
    /// The configured item count, or `None` when this modality is unlimited.
    pub fn count(&self) -> Option<usize> {
        match self {
            Self::Count(count) => Some(*count),
            Self::Options { count, .. } => *count,
        }
    }
}

评论区精华

默认值设计:999 改为无限制 设计

@BugenZhao 认为 999 不合理,建议默认无限制。@cinnamonica02 同意,指出 999 只是 Python pydantic 的默认值,Rust 无此概念。

结论:默认改为无限制(unlimited),缺席模态不设上限。 · 已解决

是否支持嵌套对象形式(如 {"video": {"count":1, "num_frames":32}}) 设计

@chatgpt-codex-connector 提出 P2 建议,认为应接受对象形式以兼容 Python 协议。@cinnamonica02 回应当前仅接受纯计数形式,但通过 extra 字段保留并转发引擎选项,后续计划跟进。

结论:当前暂不支持,但通过 extra 保留未知字段转发,计划后续 PR 支持。 · resolved (partial)

模态合并:ImageEmbeds 应归入 Image 限制 正确性

@BugenZhao 询问 ImageEmbeds 是否独立模态。@cinnamonica02 确认 Python 将两者合并计数,修正为映射到 Image 限制。

结论:ImageEmbeds 与 Image 共享同一限制计数。 · 已解决

托管引擎转发确认 question

@BugenZhao 确认参数是否被引擎读取。@cinnamonica02 详细说明 Python 端通过 EngineArgs.add_cli_args 支持,JSON 序列化后可直接传递。

结论:确认引擎端能正确接收并解析 JSON 参数。 · 已解决

类型安全:使用封闭枚举替代通用 HashMap 设计

@BugenZhao 建议使用封闭枚举以提升类型安全和错误提示。@cinnamonica02 采用,将类型定义在 chat crate 中。

结论:使用 MmLimitModality 枚举,闭联合法的键值。 · 已解决

风险与影响

风险分析

  • 模态覆盖不完整:若未来新增模态(如 depth),未在 MmLimitModality 枚举中定义,将导致解析失败或静默忽略。需要同步更新枚举和验证逻辑。
  • 序列化兼容性MmLimitSpec 使用 untagged 枚举,若 Python 端发送意外形状的 JSON 可能导致反序列化失败。额外字段通过 flatten 保留,但未验证内容正确性。
  • 托管传递路径:JSON 序列化后通过子进程参数传递,若 JSON 字符串包含特殊字符可能造成 shell 注入(但当前通过 std::process::Command 参数形式传递,风险较低)。
  • 测试覆盖不足:集成测试仅覆盖图像模态,未测试音频、视频,也未测试无限制场景和负数值等边界情况。
  • 性能影响:每个请求均执行计数验证,但复杂度为 O(num_items),对性能影响可忽略。

影响分析

  • 用户:Rust 前端用户现可限制多模态输入数量,提升 API 鲁棒性。默认行为不变(无限制),向后兼容。
  • 系统:限制在 Rust 前端即拒绝,避免无效请求到达引擎,节省资源。托管引擎也接收限制,提供双层防护。
  • 团队:代码分散在多个 crate(chat、cmd、server、managed-engine),跨模块数据流清晰;但新增类型需与 Python 端同步维护。
模态合并遗漏 序列化兼容性 托管传递路径 测试覆盖不足 类型安全

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论