Prhub

#22965 migrate CPU-only unit tests from openai_server to unit/

sgl-project/sglang · 作者 hnyls2002 · 合并时间 2026-04-16 18:53

分析状态 已生成
文件变更 5提交数 6 · 评论 3
代码增减 +92 / -93
run-ci test refactor

执行摘要

将 OpenAI 端点 CPU-only 单元测试迁移至专用目录并注册到 CPU CI 阶段。

根据 PR body 描述,目的是“Move CPU-only test classes out of GPU test suites into test/registered/unit/entrypoints/openai/, registered under stage-a-test-cpu。” 这旨在分离测试逻辑,将不依赖 GPU 的单元测试从 GPU 测试套件中移出,以避免不必要的资源消耗和简化测试分类,从而优化持续集成(CI)流程。

该 PR 对于负责测试基础设施和 CI 优化的工程师值得精读,关注点包括:测试组织策略(如何分离 CPU/GPU 测试)、mock 使用技巧(如 test_dpsk_v32_encoding_path 中的简化),以及 CI 注册配置的调整。设计决策展示了如何通过代码迁移和 stub 提高测试兼容性。

讨论亮点

review 中提出了两个主要建议:

  • 文件名误导:gemini-code-assist[bot] 指出 test_matched_stop.py 文件名与内容不符,因为文件现在只包含 TestRegexPatternMaxLength 测试,建议重命名为 test_regex_max_length.py 以更准确反映用途。
  • docstring 过时:同一评论者发现 test_serving_chat.py 中的执行指令指向旧路径 tests/test_serving_chat_unit.py,建议更新以匹配新位置。
    目前未看到这些建议被采纳或讨论结论,因此疑虑仍处于未解决状态。

实现拆解

  1. 创建新的单元测试文件:在 test/registered/unit/entrypoints/openai/test_matched_stop.py 中新增 TestRegexPatternMaxLength 测试类,专门验证正则表达式最大长度计算。使用 register_cpu_ci 注册到 stage-a-test-cpu 套件,原因是将 CPU-only 逻辑从 GPU 依赖中分离,确保测试在纯 CPU 环境下运行。
  2. 修改原有测试文件:从 test/registered/openai_server/validation/test_matched_stop.py 移除 TestRegexPatternMaxLength 类,保持该文件专注于 GPU 测试,简化职责并避免重复。
  3. 迁移其他测试文件:将 test_serving_chat.pytest_serving_completions.pytest_protocol.pyopenai_server/basic/ 移动到 unit/entrypoints/openai/,并更新导入语句。将 CI 注册从 register_cuda_ciregister_amd_ci 替换为 register_cpu_ci,因为这些测试不依赖 GPU。
  4. 添加兼容性代码:在每个迁移的文件开头添加 from sglang.test.test_utils import maybe_stub_sgl_kernelmaybe_stub_sgl_kernel() 调用,以防止导入 sgl_kernel 时触发 GPU 依赖,确保测试在 CPU 环境下兼容。
  5. 优化 mock 逻辑:在 test_serving_chat.pytest_dpsk_v32_encoding_path 测试中,用 _MockTokenizerManager mock 对象替换真实的 ServerArgs 初始化,简化测试并避免环境依赖,提高可维护性。
文件 模块 状态 重要度
test/registered/unit/entrypoints/openai/test_matched_stop.py 匹配停止测试 added 6.34
test/registered/openai_server/validation/test_matched_stop.py 匹配停止测试 modified 5.98
test/registered/unit/entrypoints/openai/test_serving_chat.py 聊天服务测试 renamed 5.7
test/registered/unit/entrypoints/openai/test_serving_completions.py 补全服务测试 renamed 4.65
test/registered/unit/entrypoints/openai/test_protocol.py 协议测试 renamed 4.47
test/registered/unit/entrypoints/openai/test_matched_stop.py test-coverage

新增文件,包含专门的 `TestRegexPatternMaxLength` 测试类,用于验证正则表达式最大长度计算,是 CPU-only 测试迁移的核心部分。

import unittestfrom sglang.srt.sampling.sampling_params import MAX_LEN, get_max_seq_length
from sglang.test.ci.ci_register import register_cpu_ci# 注册到 CPU CI 阶段,指定估计时间和套件,确保测试在纯 CPU 环境下运行
register_cpu_ci(est_time=2, suite="stage-a-test-cpu")class TestRegexPatternMaxLength(unittest.TestCase):
    """测试正则表达式模式的最大长度计算,从 GPU 套件中分离出来。"""
    @classmethod
    def setUpClass(cls):
        # 定义一组正则表达式字符串和预期最大长度的映射,涵盖无限重复、嵌套和分支等复杂情况
        cls.regex_str_to_max_len = {
            "((ab|cd(e|f){2}){3,5}g|hij)*k": MAX_LEN, # 包含 '*' 重复,表示无限长度,需要特殊处理
            "abc*?k": MAX_LEN, # 懒惰匹配 '*' 仍需要无限存储,验证函数处理
            "^spec(foo|at)$": 7, # '^' 和 '$' 不增加字符长度,计算基础字符串和选择
            "(a(bca|de(fg|hi){2,3})j){2}kl": 22, # 复杂嵌套和重复,展示最大长度累加逻辑
            "(foo(bar|baz(qux){1,2}))|(x(yz){5,10})": 21, # 分支选择,取各分支最大值
            "(((a|bc){1,3}(d(e|f){2}|gh){2,4})|(ijk|lmp(no|p){3})){5}": 90, # 多层嵌套和重复,综合验证
        }
​
    def test_get_max_length(self):
        """遍历映射表,调用 get_max_seq_length 并断言结果与预期一致。"""
        for regex_str, max_len in self.regex_str_to_max_len.items():
            if max_len == MAX_LEN:
                # 对于无限长度,确保函数返回值至少为 MAX_LEN,避免错误截断
                self.assertGreaterEqual(get_max_seq_length(regex_str), MAX_LEN)
            else:
                # 对于有限长度,精确匹配预期值,验证计算准确性
                self.assertEqual(get_max_seq_length(regex_str), max_len)if __name__ == "__main__":
    unittest.main()
test/registered/unit/entrypoints/openai/test_serving_chat.py rename-or-move

重命名并迁移文件,包含 OpenAI 聊天服务的单元测试,关键优化了 `test_dpsk_v32_encoding_path` 测试的 mock 逻辑。

from sglang.test.test_utils import maybe_stub_sgl_kernel# 在导入任何可能触发 GPU 依赖的模块前调用,确保测试在 CPU 环境下兼容
maybe_stub_sgl_kernel()import json
import unittest
from sglang.test.ci.ci_register import register_cpu_ci# 注册到 CPU CI 阶段,替换原有的 GPU 注册,明确测试不依赖 GPU
register_cpu_ci(est_time=8, suite="stage-a-test-cpu")# 在 test_dpsk_v32_encoding_path 测试中,优化 mock 逻辑:
def test_dpsk_v32_encoding_path(self):
    """测试 DeepSeek V3.2 编码路径检测,使用简化的 mock 避免真实 ServerArgs 初始化。"""
    from sglang.srt.managers.template_manager import TemplateManager
    tm = _MockTokenizerManager() # 使用内部 mock 类,而不是真实 TokenizerManager
    mock_hf_config = Mock()
    mock_hf_config.architectures = ["DeepseekV32ForCausalLM"]
    tm.model_config.hf_config = mock_hf_config
​
    # 案例1:无聊天模板 + DeepSeek V3.2 架构 -> 应使用 dpsk 编码
    tm.tokenizer.chat_template = None
    serving_chat = OpenAIServingChat(tm, TemplateManager())
    self.assertTrue(serving_chat.use_dpsk_v32_encoding)
​
    # 案例2:有聊天模板 -> 不应使用 dpsk 编码
    tm.tokenizer.chat_template = "some template"
    serving_chat = OpenAIServingChat(tm, TemplateManager())
    self.assertFalse(serving_chat.use_dpsk_v32_encoding)
​
    # 案例3:非 DeepSeek V3.2 架构 -> 不应使用 dpsk 编码
    mock_hf_config.architectures = ["LlamaForCausalLM"]
    serving_chat = OpenAIServingChat(tm, TemplateManager())
    self.assertFalse(serving_chat.use_dpsk_v32_encoding)

关键符号

TestRegexPatternMaxLength setUpClass test_get_max_length

评论区精华

文件名 `test_matched_stop.py` 可能误导 style

gemini-code-assist[bot] 指出文件现在只包含 regex 最大长度测试,建议重命名为 `test_regex_max_length.py` 以更准确反映内容,避免与同名文件混淆。

结论:未在提供材料中看到采纳或进一步讨论,疑虑未解决。 · 待处理

docstring 中的执行指令过时 documentation

gemini-code-assist[bot] 发现 `test_serving_chat.py` 的 docstring 仍指向旧路径 `tests/test_serving_chat_unit.py`,建议更新以匹配新文件位置。

结论:未在提供材料中看到修改,疑虑未解决。 · 待处理

风险与影响

  • 回归风险:测试文件迁移和 mock 调整可能导致现有测试失败,例如 test_serving_chat.py 中的 test_dpsk_v32_encoding_path 测试简化后可能覆盖不全面。
  • 兼容性风险:新增的 maybe_stub_sgl_kernel() 调用在不同环境(如本地开发或特定 CI 配置)中可能行为不一致,影响测试稳定性。
  • CI 配置风险:将测试注册到 stage-a-test-cpu 可能改变 CI 流水线的执行顺序或资源分配,需确保与其他测试阶段协调。
  • 用户影响:无直接影响,这是内部测试基础设施变更。
  • 系统影响:测试执行更高效,CPU-only 测试独立运行,减少 GPU 资源占用,可能加快 CI 反馈周期。
  • 团队影响:工程师需要更新本地测试运行命令以适应新路径,但长期看简化了测试维护和分类。
测试覆盖调整 CI 配置变更 mock 不完整风险

关联 Issue

未识别关联 Issue

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

完整报告

执行摘要

  • 一句话:将 OpenAI 端点 CPU-only 单元测试迁移至专用目录并注册到 CPU CI 阶段。
  • 推荐动作:该 PR 对于负责测试基础设施和 CI 优化的工程师值得精读,关注点包括:测试组织策略(如何分离 CPU/GPU 测试)、mock 使用技巧(如 test_dpsk_v32_encoding_path 中的简化),以及 CI 注册配置的调整。设计决策展示了如何通过代码迁移和 stub 提高测试兼容性。

功能与动机

根据 PR body 描述,目的是“Move CPU-only test classes out of GPU test suites into test/registered/unit/entrypoints/openai/, registered under stage-a-test-cpu。” 这旨在分离测试逻辑,将不依赖 GPU 的单元测试从 GPU 测试套件中移出,以避免不必要的资源消耗和简化测试分类,从而优化持续集成(CI)流程。

实现拆解

  1. 创建新的单元测试文件:在 test/registered/unit/entrypoints/openai/test_matched_stop.py 中新增 TestRegexPatternMaxLength 测试类,专门验证正则表达式最大长度计算。使用 register_cpu_ci 注册到 stage-a-test-cpu 套件,原因是将 CPU-only 逻辑从 GPU 依赖中分离,确保测试在纯 CPU 环境下运行。
  2. 修改原有测试文件:从 test/registered/openai_server/validation/test_matched_stop.py 移除 TestRegexPatternMaxLength 类,保持该文件专注于 GPU 测试,简化职责并避免重复。
  3. 迁移其他测试文件:将 test_serving_chat.pytest_serving_completions.pytest_protocol.pyopenai_server/basic/ 移动到 unit/entrypoints/openai/,并更新导入语句。将 CI 注册从 register_cuda_ciregister_amd_ci 替换为 register_cpu_ci,因为这些测试不依赖 GPU。
  4. 添加兼容性代码:在每个迁移的文件开头添加 from sglang.test.test_utils import maybe_stub_sgl_kernelmaybe_stub_sgl_kernel() 调用,以防止导入 sgl_kernel 时触发 GPU 依赖,确保测试在 CPU 环境下兼容。
  5. 优化 mock 逻辑:在 test_serving_chat.pytest_dpsk_v32_encoding_path 测试中,用 _MockTokenizerManager mock 对象替换真实的 ServerArgs 初始化,简化测试并避免环境依赖,提高可维护性。

关键文件:

  • test/registered/unit/entrypoints/openai/test_matched_stop.py(模块 匹配停止测试;类别 test;类型 test-coverage;符号 TestRegexPatternMaxLength, setUpClass, test_get_max_length): 新增文件,包含专门的 TestRegexPatternMaxLength 测试类,用于验证正则表达式最大长度计算,是 CPU-only 测试迁移的核心部分。
  • test/registered/openai_server/validation/test_matched_stop.py(模块 匹配停止测试;类别 test;类型 test-coverage;符号 TestRegexPatternMaxLength, setUpClass, test_get_max_length): 修改文件,移除了 TestRegexPatternMaxLength 类,使该文件专注于 GPU 相关的匹配停止测试,简化职责。
  • test/registered/unit/entrypoints/openai/test_serving_chat.py(模块 聊天服务测试;类别 test;类型 rename-or-move): 重命名并迁移文件,包含 OpenAI 聊天服务的单元测试,关键优化了 test_dpsk_v32_encoding_path 测试的 mock 逻辑。
  • test/registered/unit/entrypoints/openai/test_serving_completions.py(模块 补全服务测试;类别 test;类型 rename-or-move): 重命名并迁移文件,包含 OpenAI 补全服务的单元测试,更新 CI 注册并添加兼容性代码。
  • test/registered/unit/entrypoints/openai/test_protocol.py(模块 协议测试;类别 test;类型 rename-or-move): 重命名并迁移文件,包含 OpenAI 协议相关的单元测试,简化 CI 注册。

关键符号:TestRegexPatternMaxLength, setUpClass, test_get_max_length

关键源码片段

test/registered/unit/entrypoints/openai/test_matched_stop.py

新增文件,包含专门的 TestRegexPatternMaxLength 测试类,用于验证正则表达式最大长度计算,是 CPU-only 测试迁移的核心部分。

import unittestfrom sglang.srt.sampling.sampling_params import MAX_LEN, get_max_seq_length
from sglang.test.ci.ci_register import register_cpu_ci# 注册到 CPU CI 阶段,指定估计时间和套件,确保测试在纯 CPU 环境下运行
register_cpu_ci(est_time=2, suite="stage-a-test-cpu")class TestRegexPatternMaxLength(unittest.TestCase):
    """测试正则表达式模式的最大长度计算,从 GPU 套件中分离出来。"""
    @classmethod
    def setUpClass(cls):
        # 定义一组正则表达式字符串和预期最大长度的映射,涵盖无限重复、嵌套和分支等复杂情况
        cls.regex_str_to_max_len = {
            "((ab|cd(e|f){2}){3,5}g|hij)*k": MAX_LEN, # 包含 '*' 重复,表示无限长度,需要特殊处理
            "abc*?k": MAX_LEN, # 懒惰匹配 '*' 仍需要无限存储,验证函数处理
            "^spec(foo|at)$": 7, # '^' 和 '$' 不增加字符长度,计算基础字符串和选择
            "(a(bca|de(fg|hi){2,3})j){2}kl": 22, # 复杂嵌套和重复,展示最大长度累加逻辑
            "(foo(bar|baz(qux){1,2}))|(x(yz){5,10})": 21, # 分支选择,取各分支最大值
            "(((a|bc){1,3}(d(e|f){2}|gh){2,4})|(ijk|lmp(no|p){3})){5}": 90, # 多层嵌套和重复,综合验证
        }
​
    def test_get_max_length(self):
        """遍历映射表,调用 get_max_seq_length 并断言结果与预期一致。"""
        for regex_str, max_len in self.regex_str_to_max_len.items():
            if max_len == MAX_LEN:
                # 对于无限长度,确保函数返回值至少为 MAX_LEN,避免错误截断
                self.assertGreaterEqual(get_max_seq_length(regex_str), MAX_LEN)
            else:
                # 对于有限长度,精确匹配预期值,验证计算准确性
                self.assertEqual(get_max_seq_length(regex_str), max_len)if __name__ == "__main__":
    unittest.main()

test/registered/unit/entrypoints/openai/test_serving_chat.py

重命名并迁移文件,包含 OpenAI 聊天服务的单元测试,关键优化了 test_dpsk_v32_encoding_path 测试的 mock 逻辑。

from sglang.test.test_utils import maybe_stub_sgl_kernel# 在导入任何可能触发 GPU 依赖的模块前调用,确保测试在 CPU 环境下兼容
maybe_stub_sgl_kernel()import json
import unittest
from sglang.test.ci.ci_register import register_cpu_ci# 注册到 CPU CI 阶段,替换原有的 GPU 注册,明确测试不依赖 GPU
register_cpu_ci(est_time=8, suite="stage-a-test-cpu")# 在 test_dpsk_v32_encoding_path 测试中,优化 mock 逻辑:
def test_dpsk_v32_encoding_path(self):
    """测试 DeepSeek V3.2 编码路径检测,使用简化的 mock 避免真实 ServerArgs 初始化。"""
    from sglang.srt.managers.template_manager import TemplateManager
    tm = _MockTokenizerManager() # 使用内部 mock 类,而不是真实 TokenizerManager
    mock_hf_config = Mock()
    mock_hf_config.architectures = ["DeepseekV32ForCausalLM"]
    tm.model_config.hf_config = mock_hf_config
​
    # 案例1:无聊天模板 + DeepSeek V3.2 架构 -> 应使用 dpsk 编码
    tm.tokenizer.chat_template = None
    serving_chat = OpenAIServingChat(tm, TemplateManager())
    self.assertTrue(serving_chat.use_dpsk_v32_encoding)
​
    # 案例2:有聊天模板 -> 不应使用 dpsk 编码
    tm.tokenizer.chat_template = "some template"
    serving_chat = OpenAIServingChat(tm, TemplateManager())
    self.assertFalse(serving_chat.use_dpsk_v32_encoding)
​
    # 案例3:非 DeepSeek V3.2 架构 -> 不应使用 dpsk 编码
    mock_hf_config.architectures = ["LlamaForCausalLM"]
    serving_chat = OpenAIServingChat(tm, TemplateManager())
    self.assertFalse(serving_chat.use_dpsk_v32_encoding)

评论区精华

review 中提出了两个主要建议:

  • 文件名误导:gemini-code-assist[bot] 指出 test_matched_stop.py 文件名与内容不符,因为文件现在只包含 TestRegexPatternMaxLength 测试,建议重命名为 test_regex_max_length.py 以更准确反映用途。
  • docstring 过时:同一评论者发现 test_serving_chat.py 中的执行指令指向旧路径 tests/test_serving_chat_unit.py,建议更新以匹配新位置。
    目前未看到这些建议被采纳或讨论结论,因此疑虑仍处于未解决状态。

  • 文件名 test_matched_stop.py 可能误导 (style): 未在提供材料中看到采纳或进一步讨论,疑虑未解决。

  • docstring 中的执行指令过时 (documentation): 未在提供材料中看到修改,疑虑未解决。

风险与影响

  • 风险:- 回归风险:测试文件迁移和 mock 调整可能导致现有测试失败,例如 test_serving_chat.py 中的 test_dpsk_v32_encoding_path 测试简化后可能覆盖不全面。
  • 兼容性风险:新增的 maybe_stub_sgl_kernel() 调用在不同环境(如本地开发或特定 CI 配置)中可能行为不一致,影响测试稳定性。
  • CI 配置风险:将测试注册到 stage-a-test-cpu 可能改变 CI 流水线的执行顺序或资源分配,需确保与其他测试阶段协调。
  • 影响:- 用户影响:无直接影响,这是内部测试基础设施变更。
  • 系统影响:测试执行更高效,CPU-only 测试独立运行,减少 GPU 资源占用,可能加快 CI 反馈周期。
  • 团队影响:工程师需要更新本地测试运行命令以适应新路径,但长期看简化了测试维护和分类。
  • 风险标记:测试覆盖调整, CI 配置变更, mock 不完整风险

关联脉络

  • PR #22910 ci: re-enable fp8 nightly benchmark configs: 同样涉及 CI 配置调整,虽然针对 GPU 基准测试,但展示了仓库中测试基础设施的持续优化趋势。
  • PR #22926 [misc] Configure logging before ServerArgs.post_init: 同为重构类型 PR,涉及代码组织调整,可对比学习测试与配置的迁移策略。

参与讨论