# PR #27493 完整报告

- 仓库：`sgl-project/sglang`
- 标题：[SPEC] feat: init adaptive spec params from config
- 合并时间：2026-06-11 02:55
- 原文链接：http://prhub.com.cn/sgl-project/sglang/pull/27493

---

# 执行摘要

- 一句话：自适应推测解码参数初始化前移并统一
- 推荐动作：建议仔细审核：重点关注 `_init_adaptive_speculative_params` 的默认值选取逻辑是否符合预期，并验证非自适应模式下 `auto_choose_speculative_params` 的调用条件是否覆盖所有场景。此重构值得精读，展示了如何将分散的参数初始化逻辑集中到 hook 中。

# 功能与动机

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

# 实现拆解

1. **前置自适应初始化**：在 `speculative_hook.py` 的 `handle_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.py` 的 `auto_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`（模块 推测解码；类别 source；类型 dependency-wiring；符号 _init_adaptive_speculative_params, _auto_choose_speculative_params）: 核心修改文件，新增了两个函数并调整了控制流，将自适应初始化前置。
- `python/sglang/srt/server_args.py`（模块 参数配置；类别 source；类型 core-logic；符号 auto_choose_speculative_params）: 删除了 `auto_choose_speculative_params` 函数，该逻辑移至 hook 文件。
- `python/sglang/srt/speculative/adaptive_spec_params.py`（模块 参数配置；类别 source；类型 core-logic；符号 validate_adaptive_initial_steps）: 删除了 `validate_adaptive_initial_steps` 函数，并将 topk 判断条件增加 None 检查。
- `test/registered/unit/server_args/test_server_args.py`（模块 参数配置；类别 test；类型 test-coverage；符号 TestAdaptiveSpecArgs, test_adaptive_defaults_to_config_step_when_spec_params_omitted）: 新增 TestAdaptiveSpecArgs 测试类，验证自适应参数从配置初始化的行为。
- `test/registered/unit/spec/test_adaptive_spec_params.py`（模块 参数配置；类别 test；类型 test-coverage；符号 TestValidateAdaptiveInitialSteps, test_accepts_value_from_any_slot）: 删除了已移除函数的测试类 `TestValidateAdaptiveInitialSteps`。
- `test/registered/spec/eagle/test_adaptive_speculative.py`（模块 推测解码；类别 test；类型 test-coverage）: 移除了手动设置推测参数的命令行参数，让新逻辑自动推断。

关键符号：_init_adaptive_speculative_params, _auto_choose_speculative_params, auto_choose_speculative_params, validate_adaptive_initial_steps

## 关键源码片段

### `python/sglang/srt/arg_groups/speculative_hook.py`

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

```python
# python/sglang/srt/arg_groups/speculative_hook.py

def _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`

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

```python
# 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)

```

# 评论区精华

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

- 缺少 SimpleNamespace 导入导致 NameError (testing): 已在后续提交中修复，添加了 `from types import SimpleNamespace`。

# 风险与影响

- 风险：核心变更在参数初始化路径，若新逻辑与旧逻辑行为不一致可能导致自适应推测解码启动失败或参数错误。具体风险：
 - `_init_adaptive_speculative_params` 中默认 step 选取 `candidate_steps` 的中位数，而之前无配置时使用 `auto_choose_speculative_params` 的静态默认值（如 Llama 为 5），行为变化可能影响性能。
 - 在非自适应模式下，`auto_choose_speculative_params` 的调用条件变严格：仅当 `not speculative_adaptive` 且 `speculative_num_steps is None` 时才执行，与之前 always 执行不同，若某分支遗漏可能导致参数仍为 None。
 - 测试覆盖率有限：新增测试仅覆盖配置路径，未覆盖自适应关闭时的 fallback 分支。
 - 影响：**用户**：无直接体验变化，但自适应推测解码的默认行为可能轻微改变（step 选取逻辑不同）。
**系统**：参数初始化流程更清晰，依赖关系内聚到 hook 中，便于后续扩展。
**团队**：代码结构更合理，测试覆盖了核心路径，但需要留意非自适应场景的回归。

- 风险标记：默认值选取逻辑变化 , 非自适应模式 fallback 路径可能遗漏

# 关联脉络

- PR #27799 [Spec] `NGRAMWorker` on `BaseSpecWorker`; algo-owned verify-tree shape params: 同样是推测解码参数初始化相关的重构，体现了将参数逻辑统一到 hook 层的趋势。