恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

SGLang前缀缓存底层揭秘:RadixAttention如何用一棵树让推理吞吐飙升5倍

  • 首页
  • 资讯中心
  • /
  • SGLang前缀缓存底层揭秘:RadixAttention如何用一棵树让推理吞吐飙升5倍

相关资讯

让AI智能体自动拆解复杂任务,AgentScope三步上手全攻略 2026/8/21 17:15:58
把录音变成文字的免费离线工具:Buzz 完整上手体验指南 2026/8/21 17:15:58
启用与禁用有讲究:pretty_backtrace 的 enable/disable 作用域控制完全指南 2026/8/21 17:15:58

最新资讯

微信数据解析工具 PyWxDump:解密成功率从15%到98%,它做对了什么?
5分钟跑通第一个C++程序:小熊猫Dev-C++这款轻量级C++IDE体验
ROS常用消息之Imu
Umi-OCR零基础实战:3个任务让你搞定截图、批量与PDF识别
Umi-OCR 离线识别完整攻略:3 步跑起来,截图、批量、PDF 文档一条龙
碧蓝航线自动化脚本 Alas 上手方案:5 分钟装好挂机脚本,日常交给它托管

今日推荐

OpenCode AI编程助手:从核心原理到本地部署的完整实践指南
基于SpringBoot与Vue的企业资产与采购管理系统设计与实现(程序+文档+讲解)
Linux命令-uucico(UUCP传输程序)

本周热门

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码
【双层规划,节点出清价,绿证交易,CVaR方法】两级电力市场环境下计及风险的省间交易商最优购电模型附Matlab代码
隐式mpc+自适应mpc+时变mpc,线性时变模型预测控制附Simulink仿真

本月精选

如何用DamaiHelper实现演唱会门票的智能自动化抢购:完整技术解决方案指南
第4篇:59 倍性能差距的索引瓶颈定位——一次教科书级的全表扫描调优
终极歌词批量下载神器:5分钟解决离线音乐库歌词同步难题

SGLang前缀缓存底层揭秘:RadixAttention如何用一棵树让推理吞吐飙升5倍

发布时间:2026/8/21 17:15:58
SGLang前缀缓存底层揭秘:RadixAttention如何用一棵树让推理吞吐飙升5倍 SGLang前缀缓存底层揭秘RadixAttention如何用一棵树让推理吞吐飙升5倍【免费下载链接】sglangSGLang is a high-performance serving framework for large language models and multimodal models.项目地址: https://gitcode.com/GitHub_Trending/sg/sglang如果你运营过一个大模型推理服务大概率经历过这样的场景同样一段系统提示词成百上千个请求每天都在重复计算多轮对话的上下文被完整重算一遍又一遍GPU 算力和电费就这么白白烧掉。SGLang 正是为这类问题而生的高性能 LLM 服务框架而它给出的答案之一就是本文将彻底拆解的RadixAttention 前缀缓存机制。这篇文章不是概念科普而是一次源码级的解剖。我们会从痛点出发走完为什么需要 → 数据结构怎么设计 → 三个核心操作怎么实现 → 进阶特性如何演进 → 实战怎么用的完整链路。读完后你不仅能看懂 SGLang 前缀缓存的工作原理还能直接上手配置和调优。一场正在发生的重复劳动事故先看一组让你肉疼的数字示意数据仅说明数量级一个 7B 模型prefill 阶段每个 token 的耗时约是 decode 阶段的10~20 倍如果你的请求中 80% 都是共享的系统提示词那么有接近60%~70% 的 prefill 计算都在做无用功长提示词场景下TTFT首 token 延迟会因为重复计算而直接翻倍。问题的根源在于 Transformer 的KV 缓存Key-Value Cache模型在生成时会把历史 token 的 K、V 向量缓存下来避免重复计算。听起来很美好但传统实现里每个请求的 KV 缓存是彼此独立的。于是100 个请求共用同一个 2000 token 的系统提示词模型就要把这段前缀完整跑 100 遍。这就像 100 个人同时去复印同一本 2000 页的书——明明可以复印一份大家共享现实却是每人复印一本。前缀缓存的价值就是把每人复印一本变成共享同一本只补印各自不同的结尾。RadixAttention把 KV 缓存组织成一棵会共享的树为什么是基数树而不是哈希表或链表要复用前缀最朴素的思路是用哈希表把完整前缀 → KV 地址存起来。但请求的前缀是千变万化的今天来[1,2,3,4,5]明天来[1,2,3,4,9]——两者共享前 4 个 token哈希表只能做整串精确匹配共享部分完全浪费。基数树Radix Tree则完美解决这个问题它把公共前缀压缩到一个节点里任意两个请求只要前缀相同就复用同一条树路径上的 KV 缓存。SGLang 中这套管理机制叫 RadixAttention核心实现位于python/sglang/srt/mem_cache/radix_cache.py。其节点结构如下源码简化注释版class TreeNode: counter 0 def __init__(self, idNone, priority0): self.children defaultdict(TreeNode) # 子节点按首 token/page 分叉 self.parent: TreeNode None # 父指针用于向上回溯 self.key: RadixKey None # 本节点覆盖的 token id 序列 self.value: Optional[torch.Tensor] None # 对应 KV cache 索引GPU 地址 self.lock_ref 0 # 引用锁0 表示正被请求占用禁止淘汰 self.last_access_time time.monotonic()# 最近访问时间供 LRU 等策略使用 self.hit_count 0 # 命中次数供 LFU 等策略使用 self.host_value: Optional[torch.Tensor] None # 主机端备份HiCache 使用 self.hash_value: Optional[List[str]] None # 每页的哈希值用于外部缓存联动注意key的类型是RadixKey而不是裸的 list——它额外携带了extra_key用于区分不同 LoRA/适配器的命名空间和is_bigramEAGLE 投机解码的二元组视图等信息这一点后面会展开讲。下面这张图展示了三组请求在树中的共享关系token id 仅示意经验之谈当请求 2 到达时它沿着树向下匹配命中了共享节点 A 和 C只对[7]这一个 token 做 prefill其余 5 个 token 的 KV 全部复用。这就是前缀缓存的魔力——匹配得越多省得越多。树上的三个关键动作匹配、插入、淘汰理解了结构我们来看调度器每天都在树上做的三件事。动作一匹配前缀match_prefix请求进来后调度器要回答一个核心问题当前输入的最长共享前缀有多长def match_prefix(self, params: MatchPrefixParams) - MatchResult: key params.key key, _ key.maybe_to_bigram_view(self.is_eagle) if self.disable or len(key) 0: return self._empty_match_result # 关键细节页对齐。不足一页的尾巴不参与匹配 key key.page_aligned(self.page_size) value, last_node self._match_prefix_helper(self.root_node, key) if value: value torch.cat(value) # 把沿途各节点的 KV 索引拼成一个长向量 return MatchResult( device_indicesvalue, # 命中的 KV 缓存地址 last_device_nodelast_node # 匹配终点节点后续插入/锁引用都从它开始 )这里的实现有两个值得学习的优化点页对齐page_aligned如果page_size 1前缀长度会被向下取整到 page 的整数倍。原因是 KV 缓存按页分配半页缓存无法被注意力核高效利用干脆不缓存。指数搜索 二分查找RadixKey.match()在比较两个 token 序列时先用倍增窗口做gallop粗筛再在分歧窗口内二分避免对长共享前缀做逐 token 的 Python 循环——共享 10 万 token 前缀时这是数量级的差异。踩坑提醒page_size不是越大越好。如果你的 prompt 只有 32 token 而page_size 64连一页都凑不满前缀缓存将完全失效。短提示词场景建议page_size 1。动作二插入与分裂insert匹配完成后新 token 的 KV 需要挂到树上。如果匹配终点落在某个节点的中间新请求只共享了该节点前半段树就要执行分裂操作def _split_node(self, key, child, split_len): # 场景节点 key[1,2,3,4]新请求只匹配到 [1,2] new_node TreeNode(prioritychild.priority) new_node.children {key[split_len:].child_key(self.page_size): child} new_node.parent child.parent new_node.key child.key[:split_len] # 前段key[1,2] new_node.value child.value[:split_len].clone() child.parent new_node child.key child.key[split_len:] # 后段key[3,4] child.value child.value[split_len:].clone() new_node.parent.children[key.child_key(self.page_size)] new_node return new_node分裂后原节点一分为二父节点和子节点的 KV 数据被精确切开新请求从分界点继续生长自己的分支。分裂不复制 KV 数据本体只复制索引所以代价很小。动作三淘汰evictGPU 显存是稀缺资源缓存满了怎么办RadixAttention 采用LRU 优先的堆淘汰所有可淘汰叶子节点未被引用按访问时间建立小顶堆逐批弹出、释放、摘除腾出空间给新请求。def evict(self, params: EvictParams) - EvictResult: leaves list(self.evictable_leaves) eviction_heap [(self.eviction_strategy.get_priority(node), node) for node in leaves] heapq.heapify(eviction_heap) num_evicted 0 while num_evicted params.num_tokens and len(eviction_heap): _priority, x heapq.heappop(eviction_heap) self.token_to_kv_pool_allocator.free_segment(x.value, start_pos0) num_evicted len(x.value) self._delete_leaf(x) # 从父节点摘除必要时父节点晋升为新的叶子 if len(x.parent.children) 0 and x.parent.lock_ref 0: heapq.heappush(eviction_heap, (self.eviction_strategy.get_priority(x.parent), x.parent)) return EvictResult(num_tokens_evictednum_evicted)SGLang 在这里留了灵活的扩展点淘汰策略通过evict_policy.py中的策略类注入除了经典 LRU还有基于命中次数的 LFU 变体、带保护阈值的混合策略等可按业务特征选择。为真实世界而生的三个设计细节光有树还不够生产环境远比教科书复杂。以下三个设计决定了它能否扛住线上流量。细节一引用锁lock_ref防止缓存被抢问题一个请求正在使用某段前缀 KV 做 decode此时内存紧张淘汰器差点把它物理释放——这就是经典的 use-after-free。解法inc_lock_ref会沿着匹配路径从叶子一路向上到根把路径上所有节点的lock_ref加一请求结束时dec_lock_ref逐一减回。淘汰器遇到lock_ref 0的节点会直接跳过。这对应两个重要指标指标含义evictable_size可安全回收的缓存量protected_size被请求锁定、不可回收的缓存量经验之谈排查缓存命中率异常时先看protected_size是不是长期居高不下——如果是说明请求持有锁的时间过长缓存虽然满但全是锁死的命中率自然上不去。细节二命名空间隔离extra_key让多 LoRA 不打架RadixKey里藏着一个容易被忽略的字段extra_key。不同 LoRA 适配器即使 token 序列完全相同KV 也是不同的。如果共享同一个前缀节点模型行为会串味。SGLang 用extra_key把树逻辑上切成多个互不相通的命名空间同 token 前缀、不同适配器绝不共享缓存。采样盐值、检索增强上下文等不该共享的场景同样适用。细节三KV 事件BlockStored/BlockRemoved多卡一致性分布式推理中多张 GPU 各自维护一棵树。SGLang 通过events.py记录BlockStored/BlockRemoved事件并附带页面哈希供跨 TP worker 同步或外部缓存系统如 LMCache、FlexKV联动保证各卡所见即所得。从一棵树到一座粮仓分块缓存与 HiCache 的演进单机单树的版本解决了一个服务实例内的问题但长序列和跨机场景又带来了新挑战。这一节的演进脉络基本就是 SGLang 前缀缓存这几年的成长史。演进一分块前缀缓存Chunked Prefix Cache长上下文场景下一个请求的 prefill 可能要持续数秒甚至更久。未完成的请求如果也能缓存后续请求就能提前复用。SGLang 的chunk_cache.py实现了对未完成请求的增量缓存请求每生成一段就把这一段挂上树cache_unfinished_req并在请求完成时做最终归并cache_finished_req。# 调度器侧简化示意未完成请求的增量缓存 def cache_unfinished_req(self, req, chunkedFalse): token_ids req.get_fill_ids() # 已填充的 token kv_indices req_to_token_pool.req_to_token[req.req_pool_idx, :len(token_ids)] radix_key RadixKey(token_ids, req.extra_key).page_aligned(self.page_size) values kv_indices[:len(radix_key)].to(dtypetorch.int64, copyTrue) result self.insert(InsertParams(keyradix_key, valuevalues, chunkedchunked)) new_prefix_len result.prefix_len # 已插入树的区间从请求侧释放避免重复持有 self.token_to_kv_pool_allocator.free_segment( kv_indices[req.cache_protected_len:new_prefix_len], start_posreq.cache_protected_len, ) req.cache_protected_len new_prefix_len这样A 用户正在生成长回答时B 用户带着相同的开头进来直接吃到了 A 用户进行中的缓存TTFT 大幅下降。演进二HiCache 三级缓存GPU 显存再大也有限长上下文请求很容易把设备缓存挤爆。HiCache 在 RadixAttention 之上扩展出三级存储体系GPU 显存L1→ 主机内存L2→ 外部存储后端L3如 hf3fs、nixl、Mooncake 等。它的结构叫 HiRadixTree节点同时持有设备索引、主机索引和页面哈希。请求处理流程变成这样配合--enable-storage与存储后端配置HiCache 能让长上下文、多轮对话场景的缓存命中率从秒级过期变成跨会话、跨进程、跨机器的持久复用。实战案例一个多轮对话服务的调优记录纸上谈兵到此为止下面是一份可以直接抄作业的实战流程。第一步启动服务开启前缀缓存# 使用 SGLang 启动服务显式配置前缀缓存相关参数 python -m sglang.launch_server \ --model-path Qwen/Qwen2.5-7B-Instruct \ --port 30000 \ --mem-fraction-static 0.85 \ --page-size 16 \ --chunked-prefill-size 8192--page-size 16兼顾注意力核性能和缓存粒度。如果你的场景是大量短 prompt改为--page-size 1换取最大复用率--mem-fraction-static给 KV 缓存池预留更多显存缓存容量越大命中率越高--disable-radix-cache千万别加这是关掉白送的性能。第二步客户端复用前缀验证命中import requests BASE http://localhost:30000 SYSTEM 你是一位资深的后端工程师回答要简洁、准确、带代码示例。 def chat(history, user_msg): # 每次都携带完整的共享系统提示词 历史让服务端能复用前缀 messages [{role: system, content: SYSTEM}] history [ {role: user, content: user_msg} ] resp requests.post( f{BASE}/v1/chat/completions, json{model: local, messages: messages, max_tokens: 256}, ).json() return resp[choices][0][message][content] history [] for q in [用 Python 写一个 LRU 缓存, 加个线程安全版本, 再支持 TTL 过期]: answer chat(history, q) history.append({role: user, content: q}) history.append({role: assistant, content: answer})重点客户端必须在每次请求中带上完整的 system history。服务端每次都会做一次match_prefix第三轮请求时前两轮的全部 KV 都被命中只有最后一个问题在做 prefill。第三步盯住命中率指标通过 metrics 接口观察sglang:gpu_prefix_cache_hit_rate设备缓存命中率多轮对话理想值应在 70% 以上sglang:radix_cache_evictable_size/protected_size判断缓存是否假满若命中率长期偏低优先检查page_size是否过大、是否误用了extra_key导致命名空间割裂、请求是否真的共享前缀。一组示意基准数据下表展示了一个内部压测场景共享 2000 token 系统提示词 100 并发的对比结果数据为示意值仅用于说明收益量级请以你实际环境为准场景无前缀缓存默认 RadixAttention分块缓存HiCache(L2/L3)每请求重复计算 token 数~2200~600~400~100平均 TTFT3.4s1.2s0.9s0.6s吞吐req/s18465872相对加速比1.0x2.6x3.2x4.0x可以看到收益主要来自减少重复 prefill——这也是为什么共享前缀越长的场景收益越夸张。在多轮对话、批量提示工程、代码补全、文档批量摘要这四类场景中通常能获得2.5x~5x的吞吐提升。写在最后三个建议与一个方向如果你正准备在生产环境落地前缀缓存记住三句话先量命中率再调参数gpu_prefix_cache_hit_rate是一切优化的北极星指标短 prompt 用page_size1长 prompt 用 16/32并用--chunked-prefill-size配合分块缓存跨实例复用是下一个瓶颈单机 RadixAttention 再强请求被负载均衡打散后命中率也会崩这时就该考虑前缀感知路由或 HiCache 存储后端了。未来值得关注的方向包括缓存跨节点共享KV 通过 RDMA 直通、量化 KV 缓存与缓存树的协同、以及基于请求模式预测的预取策略。SGLang 的前缀缓存体系在这些方向上都已铺开实现是当前开源社区中少有的把缓存这件事做到体系化的框架。如果你也想亲手把玩这棵树可以git clone https://gitcode.com/GitHub_Trending/sg/sglang后直接运行python/sglang/srt/mem_cache/radix_cache.py文件底部的自带 demo亲眼看看一棵树如何生长、分裂与共享。行动号召今晚就给你的服务加上--page-size 16跑一轮多轮对话压测然后盯着命中率指标——我赌你会回来感谢这棵树。遇到调优问题欢迎在评论区贴出你的命中率数据和场景我们一起分析。如果这篇文章帮你省下了一台 GPU点个赞、转给正在为 TTFT 发愁的同事。下一篇我们将拆解 SGLang 的调度器与投机解码看看预测未来如何让 decode 也快起来。【免费下载链接】sglangSGLang is a high-performance serving framework for large language models and multimodal models.项目地址: https://gitcode.com/GitHub_Trending/sg/sglang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号