# PR #29645 完整报告

- 仓库：`sgl-project/sglang`
- 标题：Support real draft tokens to simulated acceptance
- 合并时间：2026-07-01 08:22
- 原文链接：http://prhub.com.cn/sgl-project/sglang/pull/29645

---

# 执行摘要

- 一句话：新增真实 draft token 模拟接受模式
- 推荐动作：值得快速阅读：PR 虽小但展示了在有兼容性约束下如何逐步改进模拟逻辑的设计思路。核心决策点是保持向后兼容的同时引入可选的正确性修复。建议后续跟进添加单元测试覆盖 `generate_simulated_accept_index` 的核心分支。

# 功能与动机

原有模拟接受逻辑在验证后使用固定的 token ID 100 填充预测缓冲区，但该 ID 未必与所选 draft 路径的验证 token 一致，导致下游解码消费不一致的 token/KV cache 状态（PR body："The fabricated token ID can therefore be paired with KV state produced for different draft tokens, making downstream decode consume inconsistent token/cache state"）。需要提供一种保持 token/KV 一致性的选项，同时不破坏依赖固定 token 行为的性能基准测试。

# 实现拆解

1. **新增环境变量**：在 `python/sglang/srt/environ.py` 中声明 `SGLANG_SIMULATE_ACC_TOKEN_MODE`，默认值为 `"fixed"`（保留旧行为）。
2. **导入配置**：在 `python/sglang/srt/speculative/spec_utils.py` 模块级读取 `SIMULATE_ACC_TOKEN_MODE`，并在 `generate_simulated_accept_index` 函数中添加对应的参数和分支逻辑。
3. **修改模拟函数**：在 `generate_simulated_accept_index` 中添加 `candidates` 和 `target_predict` 参数。当 `mode="real-draft-token"` 且 `simulate_acc_len > 1` 时，使用 `candidates` 中的真实 draft token 填充预测缓冲区的前 `simulate_acc_len-1` 个位置，最后一个 bonus token 来自 `target_predict`（目标模型 argmax 结果）。若 `mode="fixed"`，仍然填充 token ID 100，与原行为完全一致。
4. **调用方适配**：在 `python/sglang/srt/speculative/eagle_utils.py` 的 `eagle_sample` 函数中，传递 `SIMULATE_ACC_TOKEN_MODE` 和新增的 `candidates` / `target_predict` 参数。新增输入校验：`mode="real-draft-token"` 时要求 `tree_topk=1`；非贪婪采样时自动计算 `target_predict`（argmax）。
5. **测试文件被移除**：Review 中 reviewer 指出不需要专门的单元测试文件，最终移除了测试文件。

关键文件：
- `python/sglang/srt/speculative/eagle_utils.py`（模块 推测解码；类别 source；类型 core-logic；符号 eagle_sample）: 核心调用入口，在 eagle_sample 中添加了 SIMULATE_ACC_TOKEN_MODE 的校验、target_predict 的计算逻辑，并将新参数传递给 generate_simulated_accept_index。
- `python/sglang/srt/speculative/spec_utils.py`（模块 推测解码；类别 source；类型 core-logic；符号 SIMULATE_ACC_TOKEN_MODE, generate_simulated_accept_index）: 核心模拟逻辑所在文件，修改了 generate_simulated_accept_index 函数以实现真实 draft token 分支。
- `python/sglang/srt/environ.py`（模块 配置；类别 source；类型 configuration）: 声明新的环境变量 SGLANG_SIMULATE_ACC_TOKEN_MODE，默认值为 "fixed"。

关键符号：generate_simulated_accept_index, eagle_sample

## 关键源码片段

### `python/sglang/srt/speculative/eagle_utils.py`

核心调用入口，在 eagle_sample 中添加了 SIMULATE_ACC_TOKEN_MODE 的校验、target_predict 的计算逻辑，并将新参数传递给 generate_simulated_accept_index。

```python
# python/sglang/srt/speculative/eagle_utils.py
# 在 eagle_sample 函数中，模拟接受分支前新增校验

if SIMULATE_ACC_TOKEN_MODE not in ("fixed", "real-draft-token"):
    raise ValueError(
        "Invalid SGLANG_SIMULATE_ACC_TOKEN_MODE "
        f"{SIMULATE_ACC_TOKEN_MODE!r}; expected 'fixed' or 'real-draft-token'."
    )

if SIMULATE_ACC_TOKEN_MODE == "real-draft-token":
    if verify_input.tree_topk != 1:
        raise ValueError(
            "SGLANG_SIMULATE_ACC_LEN with real draft tokens currently "
            "requires speculative_eagle_topk=1."
        )
    # 对非贪婪采样，用 argmax 作为 target_predict
    if target_predict is None:
        target_predict = torch.argmax(next_token_logits, dim=-1).reshape(
            bs, verify_input.draft_token_num
        )

accept_index = generate_simulated_accept_index(
    accept_index=accept_index,
    predict=predict,          # mutable
    num_correct_drafts=num_correct_drafts,  # mutable
    candidates=candidates,
    target_predict=target_predict,
    simulate_acc_len=SIMULATE_ACC_LEN,
    simulate_acc_token_mode=SIMULATE_ACC_TOKEN_MODE,
    bs=bs,
    spec_steps=verify_input.max_tree_depth - 1,
)

```

### `python/sglang/srt/speculative/spec_utils.py`

核心模拟逻辑所在文件，修改了 generate_simulated_accept_index 函数以实现真实 draft token 分支。

```python
# python/sglang/srt/speculative/spec_utils.py
# 新增常量
SIMULATE_ACC_TOKEN_MODE = envs.SGLANG_SIMULATE_ACC_TOKEN_MODE.get()

# generate_simulated_accept_index 函数关键分支（精简）
def generate_simulated_accept_index(
    accept_index,
    predict,
    num_correct_drafts,
    candidates,
    target_predict,
    bs,
    spec_steps,
    simulate_acc_len: float = SIMULATE_ACC_LEN,
    simulate_acc_method: str = SIMULATE_ACC_METHOD,
    simulate_acc_token_mode: str = SIMULATE_ACC_TOKEN_MODE,
):
    use_real_draft_tokens = simulate_acc_token_mode == "real-draft-token"
    # ... 采样 simulate_acc_len ...
    num_correct_drafts.fill_(simulate_acc_len - 1)

    if not use_real_draft_tokens:
        predict.fill_(100)  # 旧行为：固定 token ID 100
        return sim_accept_index

    # 真实 draft token 模式：用 candidates 填充 predict 的前 simulate_acc_len-1 个位置
    if simulate_acc_len > 1:
        draft_node_indices = sim_accept_index[:, : simulate_acc_len - 1].long()
        predict[draft_node_indices] = candidates[:, 1:simulate_acc_len].to(
            dtype=predict.dtype
        )
        # 最后一个 bonus token 来自 target_predict
        bonus_node_indices = sim_accept_index[:, simulate_acc_len - 1].long()
        predict[bonus_node_indices] = target_predict[:, simulate_acc_len - 1].to(
            dtype=predict.dtype
        )
    return sim_accept_index

```

# 评论区精华

1. **默认行为争议**：nvpohanh 提议默认使用 `real-draft-token` 模式（"more correct"），允许用户通过设置 token ID 回退。weireweire 指出新方式不支持 topk>1，可能破坏旧配置。最终 Fridge003 决定保持 `fixed` 为默认值以保持向后兼容。
2. **代码健壮性**：gemini-code-assist[bot] 建议用 `"target_predict" in locals()` 判断替代依赖互补条件，但该建议未被采纳。
3. **测试文件**：Fridge003 明确要求移除单元测试文件（"We don't need this unit test"），作者随即删除。
4. **关联 PR 建议**：nvpohanh 建议合并 PR#29320，但最终未合并。

- 默认行为选择：fixed vs real-draft-token (design): 保持默认 fixed 以向后兼容，real-draft-token 作为可选模式。
- 代码健壮性：target_predict 初始化方式 (style): 未采纳，保持原有条件分支逻辑。
- 单元测试文件必要性 (testing): 移除测试文件，无自动化测试覆盖新逻辑。
- 合并关联 PR #29320 (other): 未合并，独立处理。

# 风险与影响

- 风险：
 1. **向后兼容风险低**：默认 `fixed` 模式完全保留旧逻辑，用户无感知。
 2. **`real-draft-token` 模式范围限制**：该模式要求 `topk=1`，若用户在未调整配置的情况下直接设置环境变量，会触发 ValueError。文档或用户沟通需要明确提示。
 3. **无测试覆盖**：原有的测试文件被删除，新逻辑缺少自动化验证，回归风险由人工验证和后续 CI 承担（Fridge003 说明 "CI tests don't cover simulated acc len"）。
 - 影响：**影响范围**：仅影响使用 `SGLANG_SIMULATE_ACC_LEN` 环境变量开启模拟接受的用户。
**影响程度**：中等。新增了可选的正确性改进（token/KV 一致性），默认行为不变，对现有工作流无影响。
**用户 / 团队**：需要使用模拟接受进行基准测试或调试的工程师可以启用 `real-draft-token` 模式获得更准确的模拟结果。代码维护者需注意新增的环境变量和校验逻辑。

- 风险标记：缺少测试覆盖 , 功能受限 (topk=1 only)

# 关联脉络

- PR #29320 unrelated to this PR but mentioned in discussion: nvpohanh 在 review 中建议合并 PR#29320，但最终未合并，说明两者有潜在关联（可能涉及模拟接受的其他改进）。