Prhub

#25300 feat: optional caller-supplied mm_hashes on GenerateReqInput

原始 PR 作者 krishung5 合并时间 2026-06-02 02:04 文件变更 4 提交数 6 评论 4 代码增减 +133 / -0

执行摘要

可选调用方提供 mm_hashes 以实现确定性 pad_value

外部 KV 路由器(如 Dynamo)需要应用层哈希与 sglang 的 pad_value 对齐,以使路由决策与 RadixAttention 前缀缓存一致。当前 sglang 始终在 set_pad_value 内部调用 hash_feature,导致调用方提供的哈希无法生效,路由侧前缀缓存命中成为偶然。

值得精读。该 PR 展示了如何在复杂系统中添加可选调用方集成接口:清晰的文档、优雅的错误处理和完备的单元测试。设计上对十六进制字符串的选择是有远见的。

讨论亮点

该 PR 的 Review 讨论较少。主要的交互是维护者 ishandhanani 触发了 CI 重新运行,并在 CI 通过后确认 'All relevant CI has passed'。设计上选择十六进制字符串而非整数,已在 PR 描述中解释:JSON 兼容性、便于未来扩展更宽哈希。

实现拆解

实现分为以下步骤:

  1. 数据模型扩展python/sglang/srt/managers/io_struct.py):在 GenerateReqInput 数据类中添加 mm_hashes: Optional[Union[List[str], List[List[str]]]] 字段,默认值为 None,并附上详细文档。
  2. 请求解析注入python/sglang/srt/managers/tokenizer_manager.py):在 _tokenize_one_request 方法中,从 obj.mm_hashes 中解析十六进制字符串,按顺序匹配 mm_inputs 中的 MultimodalDataItem,设置其 hash 属性。处理长度不匹配(警告并忽略)、非 MultimodalDataItem 跳过(兼容性)和解析异常(记录警告,让该项在后续 set_pad_value 中自动回退)。该注入发生在原有的 set_pad_value 循环之前。
  3. 引擎 API 透传python/sglang/srt/entrypoints/engine.py):在 generateasync_generate 方法中添加 mm_hashes 参数,并将其传递给 GenerateReqInput 构造函数,实现完整链路。
  4. 向后兼容与回退:所有路径保持默认 None,对于任何异常(长度不匹配、解析失败),日志警告并忽略调用方哈希,沿用原始 hash_feature 逻辑,确保不会因错误输入阻塞请求。
  5. 测试覆盖test/registered/unit/managers/test_mm_hashes.py):新增单元测试文件,验证 GenerateReqInput.mm_hashes 接受和默认值、set_pad_value 在预置哈希时跳过 hash_feature、相同哈希产生相同 pad_value、不同哈希产生不同 pad_value。
文件 模块 状态 重要度
python/sglang/srt/managers/io_struct.py 数据模型 modified 5.52
python/sglang/srt/managers/tokenizer_manager.py 请求处理 modified 6.67
python/sglang/srt/entrypoints/engine.py 引擎层 modified 5.97
test/registered/unit/managers/test_mm_hashes.py 测试套件 added 7.62

关键符号

_tokenize_one_request generate async_generate TestMmHashesContract

关键源码片段

python/sglang/srt/managers/io_struct.py core-logic

数据模型变更,添加 mm_hashes 字段,是整个功能的入口。

# python/sglang/srt/managers/io_struct.py
# 在 GenerateReqInput 数据类中新增字段 mm_hashes
@dataclass
class GenerateReqInput(BaseReq):
    # ... 其他字段 ...
    # Optional per-image hashes the caller has already computed (hex strings,
    # one per image in `image_data`). When supplied, each MultimodalDataItem's
    # `hash` is initialised from this list and `set_pad_value` skips the
    # internal `hash_feature()` recompute, so the resulting `pad_value` is
    # deterministic from the caller's hash. Intended for external KV routers
    # that compute their own per-image hash for routing decisions and need
    # sglang's prefix-cache key to align. When unset, behavior is unchanged
    # (sglang hashes the processor feature tensor).
    mm_hashes: Optional[Union[List[str], List[List[str]]]] = None
python/sglang/srt/managers/tokenizer_manager.py core-logic

核心逻辑变更,在 _tokenize_one_request 中解析 mm_hashes 并设置到 MultimodalDataItem.hash。

# python/sglang/srt/managers/tokenizer_manager.py
# 在 _tokenize_one_request 方法中,处理完 mm_inputs 后插入
async def _tokenize_one_request(self, obj, ...):
    # ... 之前的代码处理 mm_inputs ...
​
    # 尝试使用调用方提供的 per-image hashes
    caller_mm_hashes = getattr(obj, "mm_hashes", None)
    if caller_mm_hashes and mm_inputs and mm_inputs.mm_items:
        if len(caller_mm_hashes) != len(mm_inputs.mm_items):
            # 长度不匹配警告忽略
            logger.warning(
                "mm_hashes length (%d) != mm_items length (%d); "
                "ignoring caller hashes for this request.",
                len(caller_mm_hashes),
                len(mm_inputs.mm_items),
            )
        else:
            for item, hex_hash in zip(mm_inputs.mm_items, caller_mm_hashes):
                if not isinstance(item, MultimodalDataItem):
                    continue # 跳过非标准项
                try:
                    # 将十六进制字符串转换为整数并设置到 hash
                    item.hash = int(hex_hash, 16)
                except (TypeError, ValueError):
                    # 解析失败时记录警告,后续 set_pad_value 会回退
                    logger.warning(
                        "Ignoring malformed mm_hashes entry %r; "
                        "this item will fall back to hash_feature().",
                        hex_hash,
                    )
​
    # 原有的 set_pad_value 循环,当 hash 已设置时跳过特征计算
    if envs.SGLANG_MM_PRECOMPUTE_HASH.get() and mm_inputs and mm_inputs.mm_items:
        for item in mm_inputs.mm_items:
            if isinstance(item, MultimodalDataItem):
                item.set_pad_value()
    # ... 后续代码 ...

评论区精华

CI 重运行与状态确认 other

维护者 ishandhanani 在 Issue 中评论 '/tag-and-rerun-ci' 触发 CI 重跑,后续确认 'All relevant CI has passed'。

结论:CI 通过后合并。 · 已解决

风险与影响

风险较低:

  • mm_hashes 为空时无行为变化,完全向后兼容。
  • 解析异常被捕获并回退,不会导致请求失败。
  • 可能的风险:调用方未正确使用 mm_hashes(如顺序错误)会导致路由预期与实际前缀缓存不一致,但这是使用错误而非 Bug。
  • 测试覆盖了关键路径,但缺失端到端(集成)测试。由于涉及外部路由器的协作,端到端验证有限。

影响范围中等:

  • 用户:对多模态请求,调用方可选择提供 mm_hashes 以获得确定性的前缀缓存行为,优化跨请求的缓存命中。无需变更现有调用。
  • 系统:在 tokenizer_manager 中添加了解析逻辑,性能开销极小;解析仅发生在提供 mm_hashes 时。
  • 团队:提供了与外部路由器集成的正式协议,促进 sglang 在更广泛系统中的可组合性。
核心路径变更 可选字段 缺少端到端测试

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论