Prhub

#40537 Add system_fingerprint field to OpenAI-compatible API responses

原始 PR 作者 simon-mo 合并时间 2026-04-27 16:17 文件变更 10 提交数 6 评论 7 代码增减 +225 / -2

执行摘要

添加 system_fingerprint 字段支持四种模式

PR body 指出需要支持 system_fingerprint 字段帮助客户端识别服务环境。关联 issue 评论虽提到 OpenAI 已弃用该字段,但作者认为仍有用,并通过 --fingerprint-value 支持自定义覆盖。

该 PR 代码质量较高,测试覆盖充分,review 中提出的问题均已解决。推荐阅读 fingerprint.py 了解模块设计,以及 completion/serving.py 中流式指纹注入的精确控制。整体设计考虑了兼容性、性能和信息安全,值得参考。

讨论亮点

review 评论中讨论了三个要点:
(1) 缓存键安全性:gemini-code-assist 指出使用 id(vllm_config) 作为缓存键可能在对象回收后产生错误,建议改用 WeakKeyDictionary;最终提交移除了模块级缓存,改为由 serving 类实例缓存,彻底规避该问题。
(2) 属性访问健壮性:gemini-code-assist 建议对并行配置属性使用 getattr 返回值而不是直接访问,以避免 AttributeError;实现已按建议修复。
(3) 流式响应序列化开销:Codex 提醒 exclude_unset=False 会导致每个流式块都携带 nullsystem_fingerprint;作者随后改为 exclude_unset=True 并仅在最终块显式设置字段,njhill 补充质疑此变更可能影响其他字段序列化行为,但经确认后接受。

实现拆解

  1. 新增指纹核心模块 vllm/entrypoints/openai/fingerprint.py,定义 build_system_fingerprint 函数根据模式构建指纹字符串:full 模式包含版本号、非平凡并行度(tp/pp/dp/ep)和配置哈希前缀;hash 模式仅含版本号和哈希;custom 模式返回用户指定值;none 返回 None。采用模块级全局变量 _DEFAULT_MODE_CUSTOM_VALUE 存储默认模式,通过 set_default_fingerprint_mode 在启动时配置。

  2. 在服务启动入口注入模式:修改 vllm/entrypoints/openai/generate/api_router.py,在 init_generate_state 中调用 set_default_fingerprint_mode 以应用 CLI 参数 --fingerprint-mode--fingerprint-value,确保在所有 serving 类构造前生效。

  3. 基类计算并缓存指纹:修改 vllm/entrypoints/openai/engine/serving.pyOpenAIServing.__init__,在初始化时调用 get_system_fingerprint(engine_client.vllm_config),并将结果缓存到 self.system_fingerprint。计算异常时优雅降级为 None,不阻断启动。

  4. 各响应端点注入指纹字段:分别修改 completion/serving.pychat_completion/serving.pybatch_serving.py,在非流式响应(CompletionResponseChatCompletionResponse)和流式响应的最终块(include_usage 为真时标记 usage 块,否则标记终止块)上设置 system_fingerprint。同时将序列化策略从 exclude_unset=False 改为 exclude_unset=True 以抑制未设置字段的输出。

  5. 协议模型添加字段:在 CompletionStreamResponseChatCompletionResponseCompletionResponse 等协议类中增加 system_fingerprint: str | None = None 字段定义。

  6. 测试覆盖:新增 tests/entrypoints/openai/test_fingerprint.py,验证四种模式输出格式、并行度编码、哈希失败降级、模式切换等行为,测试采用 SimpleNamespace 模拟配置对象。

文件 模块 状态 重要度
vllm/entrypoints/openai/fingerprint.py 指纹模块 added 8.29
tests/entrypoints/openai/test_fingerprint.py 测试 added 7.36
vllm/entrypoints/openai/engine/serving.py 引擎服务 modified 6.37
vllm/entrypoints/openai/completion/serving.py 补全服务 modified 5.99
vllm/entrypoints/openai/chat_completion/serving.py 聊天服务 modified 5.97
vllm/entrypoints/openai/generate/api_router.py 路由 modified 5.48
vllm/entrypoints/openai/cli_args.py 配置 modified 5.45
vllm/entrypoints/openai/completion/protocol.py 协议 modified 4.59
vllm/entrypoints/openai/chat_completion/protocol.py 协议 modified 4.59
vllm/entrypoints/openai/chat_completion/batch_serving.py 批处理 modified 3.95

关键符号

set_default_fingerprint_mode get_system_fingerprint build_system_fingerprint

关键源码片段

vllm/entrypoints/openai/fingerprint.py core-logic

核心模块,定义了指纹构建、模式设置和获取函数。

def build_system_fingerprint(
    vllm_config: Any,
    mode: FingerprintMode = "full",
    custom_value: str | None = None,
) -> str | None:
    # 如果模式为 none,直接返回 None
    if mode == "none":
        return None
    # 如果模式为 custom,返回用户指定的值
    if mode == "custom":
        return custom_value
​
    # 延迟导入版本号,避免模块级依赖
    from vllm import __version__ as vllm_version
​
    # 计算配置哈希:捕获所有异常,失败时使用 "nohash" 占位
    try:
        hash8 = vllm_config.compute_hash()[:8]
    except Exception:
        hash8 = "nohash"
​
    # hash 模式:仅版本号加哈希
    if mode == "hash":
        return f"vllm-{vllm_version}-{hash8}"
​
    # full 模式:构建包含版本号、非平凡并行度、哈希的字符串
    parts: list[str] = [f"vllm-{vllm_version}"]
    pc = getattr(vllm_config, "parallel_config", None)
    if pc is not None:
        # 只添加大于 1 的并行度,避免无意义的标注
        tp = getattr(pc, "tensor_parallel_size", 1)
        if tp > 1:
            parts.append(f"tp{tp}")
        pp = getattr(pc, "pipeline_parallel_size", 1)
        if pp > 1:
            parts.append(f"pp{pp}")
        dp = getattr(pc, "data_parallel_size", 1)
        if dp > 1:
            parts.append(f"dp{dp}")
        if getattr(pc, "enable_expert_parallel", False):
            parts.append("ep")
    parts.append(hash8)
    return "-".join(parts)
tests/entrypoints/openai/test_fingerprint.py test-coverage

单元测试,覆盖四种模式、并行度编码、哈希失败降级等。

def test_four_modes_produce_expected_shapes():
    # 构造一个模拟的 vllm_config,具有 tp=8, ep=True
    cfg = _cfg(tp=8, ep=True)
    # 验证 full 模式包含 tp8 和 ep
    assert fp.build_system_fingerprint(cfg, "full") == (
        f"vllm-{v}-tp8-ep-a3b21f94"
    )
    # 验证 hash 模式不含 tp8
    assert fp.build_system_fingerprint(cfg, "hash") == f"vllm-{v}-a3b21f94"
    # 验证 custom 模式返回自定义字符串
    assert fp.build_system_fingerprint(cfg, "custom", "my-fp") == "my-fp"
    # 验证 none 模式返回 None
    assert fp.build_system_fingerprint(cfg, "none") is None

评论区精华

使用 id(vllm_config) 作为缓存键的潜在问题 正确性

gemini-code-assist 指出 id(vllm_config) 可能因对象回收而重用,建议使用 WeakKeyDictionary 或移除缓存。

结论:后续提交移除了模块级缓存,改为 serving 类实例缓存,避免了问题。 · 已解决

并行配置属性访问的健壮性 正确性

gemini-code-assist 建议使用 getattr 返回值而非直接访问属性。

结论:作者采用了建议,修改为使用 getattr 返回值。 · 已解决

流式响应中 system_fingerprint 的序列化开销 性能

Codex 指出 exclude_unset=False 会导致每个流式块都序列化 null 字段,增加开销。njhill 补充关切 exclude_unset=True 可能影响其他字段。

结论:作者改为 exclude_unset=True,并显式在最终块设置 fingerprint,且补充了 object 字段以保持兼容。 · 已解决

风险与影响

主要风险包括:
(1) 配置哈希依赖vllm_config.compute_hash() 可能因依赖缺失或中途变更而抛出异常,但代码已捕获所有异常并降级为 'nohash',保证启动不中断。
(2) 序列化兼容性:将 exclude_unset=True 改为默认可能影响其他未显式设置的字段(如 object)在流式响应中缺失,但后续提交已显式补回 object 字段,维持兼容。
(3) 性能风险:指纹仅启动时计算一次,每请求仅读取缓存属性,无额外开销。
(4) 信息泄露:full 模式可能暴露并行度配置,但哈希模式可规避,且 custom 模式允许用户完全控制。

对客户端:非流式响应新增 system_fingerprint 字段,符合 OpenAI 规范;流式响应仅在最终块携带。对服务端:新增 CLI 参数 --fingerprint-mode--fingerprint-value,需配置人员了解。对团队:新增 84 行核心逻辑、76 行测试,维护成本低。向下兼容:指纹计算失败时字段为 None,不影响现有逻辑。

配置哈希依赖 序列化兼容性调整 新模块依赖

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论