十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SGLang前缀缓存:一次搞懂RadixAttention,让LLM多轮对话推理快数倍

SGLang前缀缓存:一次搞懂RadixAttention,让LLM多轮对话推理快数倍 SGLang前缀缓存一次搞懂RadixAttention让LLM多轮对话推理快数倍【免费下载链接】sglangSGLang is a high-performance serving framework for large language models and multimodal models.项目地址: https://gitcode.com/GitHub_Trending/sg/sglang你是不是遇到过这种情况模型推理服务并发一高响应就肉眼可见地变慢GPU 利用率却上不去多个请求明明共享同一段系统提示词服务器却一遍遍重复计算。SGLang 这个高性能大模型推理框架正是用一套名为 RadixAttention 的前缀缓存技术把这种重复劳动降到最低让推理加速数倍成为现实。一张图看懂前缀缓存到底在缓存什么LLM 每生成一个 token都要把之前所有 token 的 KVKey-Value注意力机制的键值对缓存下来。RadixAttention 的核心思想只有一句话把这份 KV 缓存按公共前缀组织成一棵共享树谁的前缀相同谁就复用同一段缓存。读完这篇文章你会收获 RadixAttention 的三个关键机制共享、命中、淘汰 零配置启用前缀缓存三分钟跑通加速 看懂缓存命中率指标量化你的提速效果⚠️ 四个最常见的坑提前帮你排掉痛点拆解没有前缀缓存时重复计算有多浪费先看两组方案对比你就明白这项技术的分量。维度A 方案每次全量重算B 方案RadixAttention 前缀缓存多轮对话每轮都要重新处理整段历史只有增量 token 需要计算批量提示工程N 个请求重复计算 N 遍系统提示词系统提示词只算一次全员共享首次响应延迟TTFT随历史长度线性增长命中后几乎恒定GPU 利用率大量算力花在重复劳动上算力集中在真正的生成上多用户服务人越多浪费越严重共享前缀越多收益越大一个直观感受当 1000 个用户都问帮我写代码要求如下……时A 方案等于把这段系统提示词算了 1000 遍B 方案只算一遍其余 999 遍直接抄作业。原理逐步拆解前缀缓存背后的三个秘密1. 把缓存串成共享线索——基数树长什么样想象一个图书馆每本书的开头几页都被复印一份挂在公共走廊里读者只需要在走廊里找到自己的开头剩下的内容才回自己座位看。基数树就是这个走廊。树上的每个节点TreeNode保存一段 token 序列及其对应的 KV 缓存索引公共前缀只存一份由所有子孙节点共享。看 SGLang 源码里的节点结构字段不多但每个都关键class TreeNode: def __init__(self, idNone, priority0): self.children defaultdict(TreeNode) # 子节点公共前缀的分叉点 self.parent None # 父节点指针 self.key None # 本节点保存的 token 段 self.value None # 这段 token 对应的 KV 缓存索引 self.lock_ref 0 # 引用锁被使用中的节点不可淘汰 self.last_access_time time.monotonic() # 最近访问时间用于 LRU 淘汰 self.host_ref_counter 0 # 主机端缓存引用计数源码位置radix_cache.py2. 命中如何发生——最长前缀匹配与断句当一个新请求进来SGLang 会拿着它的 token 序列从树根往下走找出最长的公共前缀然后只对剩下的部分做计算。这个过程叫match_prefixdef match_prefix(self, params): key params.key 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) # value 就是命中的那一段 KV 缓存索引直接用不用重算 return MatchResult(device_indicesvalue, last_device_nodelast_node, ...)还有个精妙细节如果匹配在某个节点中间停下新前缀只覆盖了节点前半段SGLang 会把节点就地劈开成两个节点让精确的前缀边界暴露出来方便后续请求复用——就像一个句子在正确的位置断句人人都能接着读。3. 缓存满了怎么办——引用锁 LRU 淘汰GPU 显存有限缓存不可能无限累积。RadixAttention 的淘汰策略是从叶子往上拔而且有两条铁律正在被请求使用的节点lock_ref 0绝不动防止读到一半被清掉只从叶子节点没有子节点的节点开始淘汰保证树的完整性。def evict(self, params): leaves list(self.evictable_leaves) # 找出所有叶子节点 heap [(self.eviction_strategy.get_priority(node), node) for node in leaves] heapq.heapify(heap) # 按 LRU/优先级 建最小堆 while num_evicted num_tokens and heap: _, x heapq.heappop(heap) # 弹出最旧的叶子 self.token_to_kv_pool_allocator.free_segment(x.value) # 释放KV缓存 self._delete_leaf(x) # 从树中摘除必要时上移父节点淘汰策略默认是 LRU最近最少使用也可切换为 LFU、SLRU 等见 server_args.py 中的radix_eviction_policy参数。动手实践三步跑通前缀缓存加速好消息是SGLang 的前缀缓存默认就是开启的零配置。你只需要做三件事。第 1 步安装并启动服务# 启动推理服务RadixAttention 前缀缓存默认开启无需任何额外参数 python -m sglang.launch_server \ --model-path Qwen/Qwen2.5-7B-Instruct \ --port 30000第 2 步用离线引擎直接验证from sglang import Engine # 创建离线推理引擎 engine Engine(model_pathQwen/Qwen2.5-7B-Instruct, page_size1) # 三个请求共享同一个系统提示词前缀 system_prompt 你是一个严谨的代码评审助手。规则先给结论再给理由。 questions [ system_prompt 请评审这段代码def f(x): return x, system_prompt 请评审这段代码def g(y): return y * 2, system_prompt 请评审这段代码def h(z): return z 1, ] # 每个请求只新增十几个 token 的计算量其余全部命中前缀缓存 for q in questions: print(engine.generate(promptq, max_new_tokens128))第 3 步看一眼命中率量化加速效果# 从服务监控接口读取前缀缓存命中率Prometheus 文本格式 import urllib.request def get_cache_hit_rate(port30000): text urllib.request.urlopen(fhttp://localhost:{port}/get_metrics).read().decode() for line in text.splitlines(): if line.startswith(gpu_prefix_cache_hit_rate): return float(line.split()[-1]) return None print(f前缀缓存命中率: {get_cache_hit_rate():.1%})命中率越高说明越多的 token 计算被省掉了。这也是 kv_events_publisher.py 里gpu_prefix_cache_hit_rate这个指标的含义。性能实测不同场景的提速幅度场景典型前缀命中率效果一句话解读多轮多用户对话70%~90%首 token 延迟降 50%~70%历史越长的对话省下的算力越多共享系统提示词批处理80%吞吐提升 3~5 倍提示词越长、请求越多收益越夸张长文档多轮问答60%~85%单请求耗时显著下降文档只读一次之后全是复用代码补全/编辑40%~70%整体响应提升明显文件头与上下文命中率可观数字为社区典型实测范围实际效果取决于共享前缀的长度与命中率。避坑指南四个最常见的坑坑 1改了 prompt命中率却一直很低现象 → 请求前缀明明是相同的缓存就是命不中。 原因 → prompt 里有时间戳、随机 ID 这类每次都在变的 token公共前缀被污染了。 解法 → 把动态部分放到提示词末尾让稳定的系统提示词始终占据前缀位置。坑 2多轮对话越聊越慢现象 → 前几轮很流畅后面响应越来越慢。 原因 → 每轮都把整个历史重新拼进 prompt而历史前缀可能因淘汰策略被挤出了缓存。 解法 → 确认radix_eviction_policy为lru默认并适当提高 KV 缓存池容量给历史留足空间。坑 3显存明明够却总在淘汰现象 → 命中率忽高忽低波动很大。 原因 → 缓存池大小与并发请求数不匹配峰值时被大量挤出。 解法 → 观察gpu_cache_usage_perc指标按实际负载调整--max-total-num-tokens。坑 4想禁用却不知道怎么关现象 → 某些特殊场景如需要完全独立的前缀隔离发现缓存串了。 原因 → 不知道有开关。 解法 → 启动参数加--disable-radix-cache即可彻底关闭前缀缓存参数定义见 server_args.py。进阶玩法两个值得尝试的扩展点1. 换淘汰策略默认 LRU 适合大多数场景如果你的请求有明确优先级可以试试--radix-eviction-policy priority让低优先级请求先被淘汰。2. DeepSeek 分块前缀缓存DeepSeek 模型家族支持--disable-chunked-prefix-cache开关默认开启分块。分块缓存把超长前缀切成块管理长序列场景下能显著降低调度开销短序列场景如感到 overhead 明显再显式关闭它。FAQ 快问快答Q1RadixAttention 和普通 KV cache 有什么区别普通 KV cache 是每请求一份互不相通RadixAttention 把缓存变成可共享的树结构相同前缀大家共用。Q2前缀缓存会影响生成质量吗完全不会。它缓存的是 KV 值本身数学上与重算结果一致只是省去了重复计算。Q3需要为前缀缓存额外买显存吗不需要单独买它复用的是已有的 KV 缓存池只是让池子的利用率大幅提升。Q4多模态模型支持吗SGLang 面向 LLM 与多模态模型前缀缓存在 token 层面生效图像编码结果同样可复用。Q5在哪里看命中率最方便服务暴露了 Prometheus 指标直接抓取gpu_prefix_cache_hit_rate或用文中的 Python 小脚本。总结现在就去试试回到开头的场景——并发一高就卡、GPU 空转、提示词反复重算。RadixAttention 前缀缓存把这些隐形浪费一次性打包解决而且默认开启、零配置接入。现在你可以做的只有一件事克隆项目仓库地址 https://gitcode.com/GitHub_Trending/sg/sglang 跑起一个带共享前缀的压测脚本亲眼看gpu_prefix_cache_hit_rate冲到 80% 以上感受推理提速数倍的快感。觉得有用的话点赞收藏方便下次直接抄作业也欢迎评论区聊聊你的命中率实测数据。下期预告《SGLang 显存管理如何用页面调度榨干每一块 GPU 内存》我们不见不散。【免费下载链接】sglangSGLang is a high-performance serving framework for large language models and multimodal models.项目地址: https://gitcode.com/GitHub_Trending/sg/sglang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表