Prhub

#50368 [Rust Frontend][gRPC] Add multimodal image inference

原始 PR 作者 connorcarpenter15 合并时间 2026-08-04 17:34 文件变更 12 提交数 3 评论 13 代码增减 +553 / -69

执行摘要

为 Rust gRPC 服务新增图像多模态推理

PR body 明确说明目的:"Add image inputs to the Rust frontend gRPC Inference service",并强调 "Reuse the existing multimodal preprocessing pipeline to fetch and process images, expand placeholder tokens, and attach engine-facing multimodal features";同时声明 "This PR implements image inference only. Audio and video support will be added in a follow-up PR"。此前 gRPC GenerateRequest 仅支持文本与 token_ids prompt,多模态能力是 HTTP 前端已有而 gRPC 侧缺失的明显缺口。review 中 BugenZhao 还指出 tonic 默认 4 MB 消息上限会拒绝带大图的请求("bump the default max_decoding_message_size to avoid typical requests being rejected"),并在合并前直接修复。

值得精读。核心看点:一是如何在 server 层不复制逻辑,仅通过 ChatLlm::prepare_media 复用既有多模态管线;二是 prepare_request 对 unary/stream 的统一收口与请求级日志设计;三是维护者直接 commit 复用 HTTP 消息上限常量的协作方式。建议结合 convert.rs 与 inference.rs 阅读,并优先跟进 SSRF host 校验的补强。

讨论亮点

核心 review 交锋集中在三处:一是 ChatLlm::prepare_media 的放置位置,BugenZhao 建议“Shall we make it a method on self.processor and perhaps delegate it here?”,作者按建议改为委托实现;二是媒体转换的简化,BugenZhao 指出“We can directly call item.modality()”,作者接受;三是对 ChatError 做 gRPC status 分类的建议,作者明确“This was intentionally deferred to a follow up PR to reduce the scope of this PR”,维护者认可并合并。消息大小上限的讨论最有价值:BugenZhao 在 proto 行内提问是否提升 max_decoding_message_size 以避免典型请求被拒,作者倾向后续加可配置 flag,BugenZhao 最后直接提交 commit“use that hardcoded value for gRPC server as well”,将 HTTP 侧 32 MB 常量复用到 gRPC。此外 depthfirst-app[bot] 标记了一个 MEDIUM 级 SSRF 告警:validate_media_uri 只校验 scheme 不校验 host,MediaConnector 的 reqwest 客户端默认跟随重定向,可被诱导访问 169.254.169.254 云 metadata 或内网服务,该问题未见维护者公开回应。

实现拆解

实现按“契约 → 转换 → 会话层 API → 服务层收口 → 测试/配置配套”五步推进:

  1. proto 契约扩展(rust/proto/inference.proto):新增 Modality 枚举(IMAGE/VIDEO/AUDIO)与 MediaItem 消息(oneof source:url/data_uri/raw_bytes,外加 mime_type、uuid 元数据字段),并在 GenerateRequest 追加 repeated MediaItem media = 14。VIDEO/AUDIO 枚举提前预留但服务端暂不实现,为后续 PR 留出契约空间;字段为纯增量,旧客户端不受影响。
  2. 转换层(rust/src/server/src/grpc/convert.rs):新增 media_parts_from_request 与 validate_media_uri。按 item.modality() 访问器分支:Unspecified 报 INVALID_ARGUMENT、非 Image 模态报 UNIMPLEMENTED 并带索引定位、缺 source 报 INVALID_ARGUMENT;URL/data URI 映射为 MediaContentPart::ImageUrl(scheme 白名单 http/https/data),raw bytes 映射为 ImageData 并透传 mime_type 与 uuid。
  3. 会话层 API(rust/src/chat/src/lib.rs 与 rust/src/chat/src/multimodal.rs):ChatRequestProcessor 新增私有 prepare_media,ChatLlm 暴露同名 pub 方法委托实现,避免在 server 层重复多模态逻辑;MultimodalModelInfo::prepare_multimodal 由私有改为 pub(crate) 可见性;对外导出 MediaContentPart 与 MmFeatures。
  4. gRPC 服务层重构(rust/src/server/src/grpc/inference.rs):新增 PreparedGrpcRequest 与统一入口 prepare_request,unary 与 streaming 共用同一准备路径;自动生成缺失的 request_id、写入 arrival_time、建立 info_span 请求级日志贯穿 preparation/submission/collection/stream;媒体场景强制 Prompt::TokenIds(placeholder 展开依赖 token_ids),否则返回 INVALID_ARGUMENT;调用 chat.prepare_media 原位展开 placeholder 并挂载 mm_features;新增 log_text_error 统一记录下游错误。
  5. 测试与配置配套:tests.rs 新增 FakeMultimodalBackend(基于 qwen2_vl 配置构建 MultimodalModelInfo)与 setup_grpc_service_with_backend 夹具(支持注入 backend 与请求断言闭包),新增 3 个测试覆盖媒体展开结果、文本 prompt 拒绝、超过 tonic 默认上限的大请求;rust/src/server/src/lib.rs 将 gRPC max_decoding_message_size 与 HTTP 侧共用 DEFAULT_REQUEST_BODY_LIMIT_BYTES(32 MB),routes.rs 同步复用该常量;cli.rs/config.rs 修正服务名注释。
文件 模块 状态 重要度
rust/src/server/src/grpc/inference.rs gRPC 服务 modified 7.9
rust/src/server/src/grpc/convert.rs 请求转换 modified 7.94
rust/src/server/src/grpc/tests.rs 测试 modified 8.18
rust/proto/inference.proto 接口契约 modified 5.06
rust/src/chat/src/lib.rs 会话层 modified 6.67
rust/src/chat/src/multimodal.rs 多模态 modified 4.72
rust/src/server/src/lib.rs 服务入口 modified 5.07
rust/src/server/src/routes.rs 请求路由 modified 4.33

关键符号

media_parts_from_request validate_media_uri prepare_request log_text_error prepare_media prepare_multimodal

分析完成后,这里会展示 LLM 生成的相对完整源码片段和详细注释。

评论区精华

prepare_media 应委托给 ChatRequestProcessor 设计

BugenZhao 建议“Shall we make it a method on self.processor and perhaps delegate it here?”,避免 ChatLlm 直接实现细节。

结论:作者按建议改为 processor 私有方法 + ChatLlm 委托,已修复。 · 已解决

媒体转换可直接调用 item.modality() style

BugenZhao 指出“We can directly call item.modality()”,无需手动 try_from 转换。

结论:作者接受并修改,已修复。 · 已解决

ChatError 是否归类为 gRPC status 设计

BugenZhao 问“Shall we also categorize the ChatError returned here?”,作者回应“This was intentionally deferred to a follow up PR to reduce the scope of this PR”。

结论:作者有意推迟到 follow-up PR,维护者接受并合并。 · deferred

日志中复制 status.message() 是否必要 style

BugenZhao 认为“Passing a copy of status.message() here doesn't seem very necessary? Same for other occurrences.”

结论:作者答复“You're right. Fixed.”,已修复。 · 已解决

提升 gRPC max_decoding_message_size 设计

BugenZhao 在 proto 行内提问“whether we should bump the default max_decoding_message_size to avoid typical requests being rejected”;作者倾向后续加可配置 flag。

结论:BugenZhao 直接提交 commit 复用 HTTP 侧 DEFAULT_REQUEST_BODY_LIMIT_BYTES(32 MB),“We can leave configurable options in the future!”。 · 已解决

SSRF:validate_media_uri 未校验目标 host 安全

depthfirst-app[bot] 标记 MEDIUM:仅校验 scheme 不校验 host,MediaConnector 的 reqwest 默认跟随重定向,可访问 169.254.169.254 云 metadata 或内网服务;建议 host/IP 校验、阻断内网地址并禁重定向。

结论:未见维护者公开回应或修复,属于遗留风险。 · unresolved

风险与影响

  1. SSRF 风险(高):convert.rs 的 validate_media_uri 仅做 scheme 白名单,未校验目标 host/IP;实际抓取发生在 vllm-chat 的 MediaConnector(reqwest 默认跟随重定向)。若 gRPC 服务部署在内网可达环境,恶意客户端可用 http://169.254.169.254/ 等地址探测云 metadata 或内网拓扑,错误信息还可能泄露探测结果。
  2. 核心路径重构回归风险(中):prepare_request 统一了 unary/stream 两条路径,行为变化点包括自动生成 request_id、arrival_time 赋值、日志结构变化,影响所有既有 gRPC 文本请求;现有测试主要覆盖新功能与部分拒绝路径,TLS、超时等旧边界未逐项回归。
  3. 行为兼容性风险:携带 media 的请求必须使用 token_ids prompt,文本 prompt + media 会直接收到 INVALID_ARGUMENT,调用方需适配;proto 字段本身向后兼容。
  4. 资源开销:gRPC 消息上限从 tonic 默认 4 MB 提升到 32 MB,raw bytes 大图会驻留服务内存;同时 URL 抓取引入出网依赖,重定向放大可能被滥用。
  5. 依赖面变化:server 新增 url 等依赖,Cargo.lock 同步更新,影响 Rust 侧构建。

用户侧:gRPC Inference 客户端获得多模态图像推理能力,支持 URL、data URI、raw bytes 三种传图方式;音频/视频请求会得到明确的 UNIMPLEMENTED 错误。系统侧:服务端新增出网抓取行为,扩大了信任边界与内存占用;请求级 tracing 日志(request_id、media_count、各阶段耗时)显著改善排障体验。团队侧:本 PR 为 audio/video 支持、媒体错误分类与可配置消息上限三个 follow-up 打好了契约与服务层基础,proto 的 Modality 枚举与 prepare_request 的收口设计降低了后续增量成本。

SSRF 面未封闭 核心请求路径重构 消息上限 4MB→32MB 媒体请求强制 token_ids

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论