Prhub

#23209 [Refactor] Move radix-cache utils onto RadixKey as methods

原始 PR 作者 hnyls2002 合并时间 2026-04-21 14:11 文件变更 9 提交数 12 评论 1 代码增减 +156 / -239

执行摘要

将基数树缓存的工具函数重构为 RadixKey 类方法,提升代码封装性和一致性。

PR body中明确说明这是一个'纯粹的重构',目的是代码整洁。将总是以RadixKey为第一个参数的函数转化为类方法,遵循面向对象设计原则,减少代码重复并提高封装性。

建议仔细阅读radix_cache.py中新增的RadixKey方法实现,特别是match方法中的bigram处理逻辑。这展示了如何将复杂的分支逻辑封装到类方法中,值得学习其设计权衡。对于涉及缓存系统的开发者,此PR是理解代码库演进的重要参考。

讨论亮点

由于没有review评论,主要讨论点在PR body中:'Non-goals - is_bigram branches inside each method are left as-is; follow-up PR will split into PlainKey/BigramKey subclasses.' 这表明团队决定暂时保留bigram分支,留待后续重构处理。

实现拆解

  1. 在RadixKey类中添加方法:在python/sglang/srt/mem_cache/radix_cache.py中,为RadixKey类新增matchchild_keyhash_page_check_compatible方法,同时删除模块级的_key_match_page_size1_key_match_pagedget_child_key_check_extra_key函数。
  2. 更新缓存子类初始化:在swa_radix_cache.pymamba_radix_cache.pyunified_radix_cache.py等文件中,移除对旧函数的导入,并删除__init__中设置self.key_match_fnself.get_child_key_fn的逻辑。
  3. 修改调用点:将所有使用self.key_match_fn(a, b)的地方改为a.match(b, page_size=self.page_size),将self.get_child_key_fn(key)改为key.child_key(self.page_size)
  4. 更新测试和文档:修改test_unified_radix_cache_unittest.py等测试文件以适配新API,并更新unified_cache_components/README.md中的文档引用。
  5. 影响:简化了缓存系统的配置,减少了部分函数的依赖,为后续拆分PlainKey/BigramKey子类铺平道路。
文件 模块 状态 重要度
python/sglang/srt/mem_cache/radix_cache.py 缓存核心 modified 8.78
python/sglang/srt/mem_cache/swa_radix_cache.py SWA 缓存 modified 6.56
python/sglang/srt/mem_cache/unified_radix_cache.py 统一缓存 modified 6.53

关键符号

RadixKey.match RadixKey.child_key RadixKey.hash_page RadixKey._check_compatible

关键源码片段

python/sglang/srt/mem_cache/radix_cache.py core-logic

定义了 RadixKey 类,本次重构的核心,将自由函数移动为类方法。

def _check_compatible(self, other: "RadixKey") -> None:
    # 检查 extra_key 是否匹配,确保 RadixKey 操作在相同上下文中进行
    if self.extra_key != other.extra_key:
        raise ValueError(
            f"RadixKey operations require matching extra_key, but got "
            f"{self.extra_key=} != {other.extra_key=}"
        )def match(self, other: "RadixKey", page_size: int = 1) -> int:
    """
    计算与other共享的逻辑单元前缀长度,结果按page_size向下取整。
    处理bigram模式(用于EAGLE推测解码)和平常模式,适应不同页面大小。
    """
    self._check_compatible(other)
    t0, t1 = self.token_ids, other.token_ids
​
    if self.is_bigram:
        # 在 bigram 模式下,比较原始 token;L 个匹配 token 意味着 L-1 个匹配 bigram
        i = 0
        for a, b in zip(t0, t1):
            if a != b:
                break
            i += 1
        matched = max(0, min(i - 1, len(self), len(other)))
        # 根据 page_size 对齐匹配长度
        return (matched // page_size) * page_size if page_size > 1 else matched
​
    if page_size == 1:
        # 页面大小为 1 时,直接比较 token
        i = 0
        for a, b in zip(t0, t1):
            if a != b:
                break
            i += 1
        return i
​
    # 页面大小大于 1 时,按块比较
    min_len = min(len(self), len(other))
    i = 0
    while i < min_len:
        if t0[i : i + page_size] != t1[i : i + page_size]:
            break
        i += page_size
    return idef child_key(self, page_size: int = 1):
    """
    生成前page_size个逻辑单元的哈希键,用于在基数树中查找子节点。
    在bigram模式下,键为元组对;extra_key用于命名空间隔离。
    """
    t = self.token_ids
    if self.is_bigram:
        if page_size == 1:
            plain = (t[0], t[1])
        else:
            plain = tuple((t[j], t[j + 1]) for j in range(page_size))
    else:
        plain = t[0] if page_size == 1 else tuple(t[:page_size])
    return plain if self.extra_key is None else (self.extra_key, plain)def hash_page(self, start: int, end: int, prior_hash: Optional[str] = None) -> str:
    """
    计算逻辑单元[start, end)的SHA256哈希值,用于缓存一致性验证。
    在bigram模式下,输入重叠的(t_i, t_{i+1})字节对。
    """
    hasher = hashlib.sha256()
    if prior_hash:
        hasher.update(bytes.fromhex(prior_hash))
    t = self.token_ids
    if self.is_bigram:
        for j in range(start, end):
            hasher.update(t[j].to_bytes(4, byteorder="little", signed=False))
            hasher.update(t[j + 1].to_bytes(4, byteorder="little", signed=False))
    else:
        for j in range(start, end):
            hasher.update(t[j].to_bytes(4, byteorder="little", signed=False))
    return hasher.hexdigest()

评论区精华

Bigram 分支处理策略 设计

PR body 中提到 'is_bigram branches inside each method are left as-is; follow-up PR will split into PlainKey/BigramKey subclasses.'

结论:决定在当前 PR 中保留 bigram 分支,后续通过子类拆分来进一步重构。 · 已解决

风险与影响

主要风险在于重构可能引入细微的逻辑错误,尤其是在匹配和哈希计算的核心路径上。由于变更涉及多个文件和符号,需要确保所有调用点都正确更新了参数传递(特别是page_size)。此外,删除旧函数可能影响依赖这些函数的其他未更新代码,但本PR已全面覆盖相关模块。

对终端用户无直接影响,API保持兼容。系统性能应保持不变,但代码结构更清晰,便于未来维护和扩展。开发团队需要熟悉新的RadixKey方法调用方式,这可能影响后续开发工作流。

核心路径变更 跨模块影响 缺少 review 讨论

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论