Prhub

#27493 [SPEC] feat: init adaptive spec params from config

原始 PR 作者 alphabetc1 合并时间 2026-06-11 02:55 文件变更 6 提交数 5 评论 7 代码增减 +107 / -94

执行摘要

自适应推测解码参数初始化前移并统一

在启用自适应推测解码时,需要更早地根据配置文件初始化 speculative_num_steps 等参数,避免在算法特定处理后覆盖或丢失配置值。此外,将 auto-default 选择逻辑从 server_args 中移入 hook,使依赖关系更清晰,便于维护和测试。

建议仔细审核:重点关注 _init_adaptive_speculative_params 的默认值选取逻辑是否符合预期,并验证非自适应模式下 auto_choose_speculative_params 的调用条件是否覆盖所有场景。此重构值得精读,展示了如何将分散的参数初始化逻辑集中到 hook 中。

讨论亮点

两条 review 评论均指出 test_server_args.py 中新测试类使用了 SimpleNamespace 但缺少导入,会导致 NameError。该问题已在后续提交中修复(添加了 from types import SimpleNamespace)。除此之外无其他争议。

实现拆解

  1. 前置自适应初始化:在 speculative_hook.pyhandle_speculative_decoding 中,将自适应参数的检查和初始化挪到算法分发之前,保证配置优先。
  2. 新函数抽取:新增 _init_adaptive_speculative_params:根据 speculative_adaptive_config 解析候选 steps,若用户未设置则取中位数作为默认值,并自动推导 num_draft_tokens = num_steps + 1、强制 eagle_topk=1;新增 _auto_choose_speculative_params:将原本在 server_args.pyauto_choose_speculative_params 函数整体搬入,并接受 model_arch 参数,减少对全局状态的隐式依赖。
  3. 删除冗余函数:从 server_args.py 删除 auto_choose_speculative_params(已迁移);从 adaptive_spec_params.py 删除 validate_adaptive_initial_steps(其校验逻辑已内联到 _init_adaptive_speculative_params)。
  4. 容错性增强:在 adaptive_unsupported_reason 中,speculative_eagle_topk 的比较增加了 is not None 检查,避免在 topk 未设置时错误地返回不支持。
  5. 测试覆盖:在 test_server_args.py 新增 TestAdaptiveSpecArgs 类,验证当用户省略所有自适应参数时能正确从配置中选取默认 step;在 test_adaptive_spec_params.py 中删除已被移除的 TestValidateAdaptiveInitialSteps;在 test_adaptive_speculative.py 的集成测试中移除了手动设置 --speculative-num-steps 等参数,交由新逻辑自动推断。
文件 模块 状态 重要度
python/sglang/srt/arg_groups/speculative_hook.py 推测解码 modified 7.82
python/sglang/srt/server_args.py 参数配置 modified 7.03
python/sglang/srt/speculative/adaptive_spec_params.py 参数配置 modified 6.5
test/registered/unit/server_args/test_server_args.py 参数配置 modified 6.09
test/registered/unit/spec/test_adaptive_spec_params.py 参数配置 modified 5.37
test/registered/spec/eagle/test_adaptive_speculative.py 推测解码 modified 3.95

关键符号

_init_adaptive_speculative_params _auto_choose_speculative_params auto_choose_speculative_params validate_adaptive_initial_steps

关键源码片段

python/sglang/srt/arg_groups/speculative_hook.py dependency-wiring

核心修改文件,新增了两个函数并调整了控制流,将自适应初始化前置。

# python/sglang/srt/arg_groups/speculative_hook.pydef _init_adaptive_speculative_params(server_args: "ServerArgs") -> None:
    from sglang.srt.speculative.adaptive_spec_params import resolve_candidate_steps_from_config
​
    # 从配置文件获取所有候选 steps
    candidate_steps = resolve_candidate_steps_from_config(
        cfg_path=server_args.speculative_adaptive_config,
    )
​
    # 自适应模式强制 topk=1
    if server_args.speculative_eagle_topk is None:
        server_args.speculative_eagle_topk = 1
​
    # 若用户未显式设置 num_steps,取中位数作为初始值
    if server_args.speculative_num_steps is None:
        server_args.speculative_num_steps = candidate_steps[len(candidate_steps) // 2]
​
    # 如果用户设置了 but 不在候选列表中,报错
    if server_args.speculative_num_steps not in candidate_steps:
        raise ValueError(
            f"--speculative-num-steps={server_args.speculative_num_steps} "
            f"is not in the adaptive config candidate_steps {candidate_steps}. "
            "Pass one of those values."
        )
​
    # 推导 num_draft_tokens = num_steps + 1
    server_args.speculative_num_draft_tokens = server_args.speculative_num_steps + 1
​
​
def _auto_choose_speculative_params(
    server_args: "ServerArgs", model_arch: str
) -> tuple:
    """按架构选择默认推测参数,逻辑从 server_args.py 迁移而来。"""
    if server_args.speculative_algorithm == "STANDALONE":
        return (3, 1, 4)
    if model_arch == "LlamaForCausalLM":
        return (5, 4, 8)
    elif model_arch in [
        "DeepseekV32ForCausalLM",
        "DeepseekV3ForCausalLM",
        # ... 其他架构
    ]:
        return (3, 1, 4)
    elif model_arch in ["Grok1ForCausalLM", "Grok1VForCausalLM"]:
        return (5, 4, 8)
    else:
        return (3, 1, 4)
test/registered/unit/server_args/test_server_args.py test-coverage

新增 TestAdaptiveSpecArgs 测试类,验证自适应参数从配置初始化的行为。

# test/registered/unit/server_args/test_server_args.py
class TestAdaptiveSpecArgs(CustomTestCase):
    def test_adaptive_defaults_to_config_step_when_spec_params_omitted(self):
        # 创建临时配置文件
        with tempfile.NamedTemporaryFile("w", suffix=".json") as f:
            json.dump(
                {
                    "1": {"candidate_steps": [1, 3, 5]},
                    "8": {"candidate_steps": [1]},
                },
                f,
            )
            f.flush()
​
            args = ServerArgs(model_path="dummy")
            args.speculative_algorithm = "EAGLE"
            args.speculative_adaptive = True
            args.speculative_adaptive_config = f.name
            args.device = "cuda"
            # 模拟模型架构
            args.get_model_config = lambda: SimpleNamespace(
                hf_config=SimpleNamespace(
                    architectures=["LlamaForCausalLM"],
                    get_text_config=lambda: SimpleNamespace(),
                )
            )
​
            handle_speculative_decoding(args)
​
        self.assertTrue(args.speculative_adaptive)
        self.assertEqual(args.speculative_eagle_topk, 1)
        # 中位数为 [1,3,5] 的中间索引 1 -> 3
        self.assertEqual(args.speculative_num_steps, 3)
        self.assertEqual(args.speculative_num_draft_tokens, 4)

评论区精华

缺少 SimpleNamespace 导入导致 NameError 测试

两条评论均指出新测试类使用了 `SimpleNamespace` 但未导入,会将运行时导致 NameError。

结论:已在后续提交中修复,添加了 `from types import SimpleNamespace`。 · 已解决

风险与影响

核心变更在参数初始化路径,若新逻辑与旧逻辑行为不一致可能导致自适应推测解码启动失败或参数错误。具体风险:

  • _init_adaptive_speculative_params 中默认 step 选取 candidate_steps 的中位数,而之前无配置时使用 auto_choose_speculative_params 的静态默认值(如 Llama 为 5),行为变化可能影响性能。
  • 在非自适应模式下,auto_choose_speculative_params 的调用条件变严格:仅当 not speculative_adaptivespeculative_num_steps is None 时才执行,与之前 always 执行不同,若某分支遗漏可能导致参数仍为 None。
  • 测试覆盖率有限:新增测试仅覆盖配置路径,未覆盖自适应关闭时的 fallback 分支。

用户:无直接体验变化,但自适应推测解码的默认行为可能轻微改变(step 选取逻辑不同)。
系统:参数初始化流程更清晰,依赖关系内聚到 hook 中,便于后续扩展。
团队:代码结构更合理,测试覆盖了核心路径,但需要留意非自适应场景的回归。

默认值选取逻辑变化 非自适应模式 fallback 路径可能遗漏

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论