资讯动态

深入SGLang:RadixAttention前缀复用与结构化输出优化

发布时间:2026/10/3 4:18:53 来源:尧图企业网站定制
从去年开始我陆续部署过好几个LLM推理服务vLLM、TGI都用过一段。说实话模型越来越大业务方的要求也越来越刁既要一轮对话里多个分支并行采样又要输出严格合法的JSON还嫌首字延迟太高。有一个场景让我印象特别深——同一段很长的系统提示词配上不同用户输入在线服务同时涌进来几十路请求显存直接翻倍GPU利用率却上不去。换哪个框架都差不多瓶颈不在算力而在重复计算。后来我认真梳理了一遍SGLang的架构设计发现这个框架的解题思路和vLLM完全不同。它没有把所有精力都花在显存page管理上而是从头到尾在处理一个问题让推理过程里所有能复用的计算尽可能复用。SGLang全称是Structured Generation Language for Large Language Models核心由前端语言层、运行时SRTSGLang Runtime和RadixAttention组成。这篇文章我想从架构设计的视角把它为什么这样做、每个核心组件怎么实现的、部署时哪些参数真正影响性能掰开揉碎讲清楚。适合正在做推理服务选型、或者想优化现有LLM服务吞吐的工程师参考。1. 从线上事故说起LLM推理服务的高延迟到底卡在哪先描述一个我真实遇到的场景。我们当时做一个文档问答助手系统提示词有将近两千个token用户会在同一个文档下连续追问。上线之前压测结果还不错一旦切到多轮对话加并发请求平均首字延迟从300毫秒飙升到2秒甚至出现请求排队超时。1.1 前缀重复计算的算力浪费问题出在一个容易被忽略的环节——前缀重复计算。多轮对话里每轮请求都会把历史对话拼接在用户输入前面。传统推理服务把每条请求当作独立任务同一个文档前缀、同样的系统提示词在每一轮、每一个并发请求里都要重新走一遍attention计算。我用一个简单公式说明成本。假设请求总长L其中共享前缀长度为P则每条请求实际重复计算的前缀占比约P/L。在线服务的典型场景里P/L经常超过70%。也就是说GPU每做10次FLOP7次在算一模一样的东西。即使vLLM用PagedAttention把KV Cache的显存利用率做得很高它也没有从语义层面识别“前缀相同”这件事。1.2 结构化输出的落地困境另一个痛点是结构化输出。业务方要求返回JSON格式的结果之前我们只能在prompt里写“请严格按JSON格式返回”然后在后处理环节用正则或者json.loads去解析。效果大家应该都体会过模型心情好就规规矩矩给你JSON心情不好多输出一句解释整条结果就报废了还得让用户再问一次。这本质上不是模型能力问题而是推理框架没有把“格式约束”渗透进解码过程。每个token的采样都是一个独立的概率分布框架如果不干预模型自然倾向于自由文本。SGLang在架构层面设计了约束解码机制等于在每一步token生成前先根据JSON Schema或正则表达式算出一个合法token集合再从这个集合里采样。这样生成出来的结果格式一定是合法的只要prompt足够清晰内容质量也不会因为格式约束而明显下降。1.3 多次并行采样的倍增效应还有一个被低估的场景是并行采样。做RLHF推理、或者用LLM做候选生成时同一个prompt往往要同时生成4到8个不同的回答。传统框架里这8条请求互不感知前面那段共享的prompt被重复计算8次。SGLang把这类请求看成同一个树状结构的多个分支共享路径只需要计算一次分叉之后才真正并行。这种设计思路带来的收益在多轮对话与并行采样结合的场景里尤其明显。2. 整体架构分层前端语言、运行时与调度逻辑是如何协作的SGLang的架构设计有一个很鲜明的特点前端和后端职责分离得非常清楚。前端是一个Python定义的领域特定语言DSL负责描述“生成流程”后端是一个高性能的C运行时负责把这些流程编译成高效的token级调度策略。2.1 前端语言层用Python描述生成流程SGLang的前端允许你把一次推理过程定义成一个带流程的function。比如我想实现“先让模型判断意图再根据意图生成回复”import sglang as sgl sgl.function def chat_pipeline(s, question): s sgl.user(请判断以下问题的意图回复“天气”或“闲聊”\n question) s sgl.assistant(sgl.gen(intent, max_tokens16)) if s[intent].strip() 天气: s sgl.user(请用一句话回答天气问题 question) else: s sgl.user(请用轻松的语气回应 question) s sgl.assistant(sgl.gen(answer, temperature0.7))这段代码看起来是普通Python但实际执行时SGLang会把整个流程编译成一张token生成图。if判断是在拿到第一个gen结果的token之后才执行的分支控制。这个能力很关键它意味着业务逻辑可以进入生成流程内部而不是在框架外部做多次串行请求。2.2 Radical Attention缓存层请求与缓存树的交互中枢前端负责生成逻辑真正执行推理的是SRT运行时。运行时里最核心的模块是RadixAttention缓存层它维护一棵全局的基数树。每条请求进入系统后会把输入token序列在树上做一次最长前缀匹配。命中的路径KV Cache直接复用未命中的路径才需要重新计算。这个设计消除了前缀重复计算的浪费也是SGLang在架构层面区别于vLLM最本质的一点。2.3 调度器按缓存命中率动态排序请求光有缓存还不够调度器必须知道怎么用缓存。SGLang的调度器采用cache-aware策略——不是简单地按到达时间排队而是优先调度那些能命中更多缓存的请求。有相同前缀的请求会被聚合到同一个批次里共享一次前缀前向计算。这种调度策略在并发多分支采样场景下能显著降低GPU的空转时间。3. RadixAttention把“重复计算”变成“缓存命中”的前缀复用机制聊完了整体架构接下来该深入SGLang的核心组件——RadixAttention。这个机制是SGLang高性能的基础我可以把它的工作原理说得更细一些。3.1 为什么是基数树而不是哈希表RadixAttention底层用的数据结构是基数树Radix Tree。学过数据结构的同学应该记得基数树是一种压缩前缀树。它和普通前缀树最大的区别在于如果有多个孩子节点有公共前缀这个公共前缀会被合并到父节点中。放到KV Cache场景理解假设有三条请求共享同一个很长的系统提示词提示词内部可能有几个边界不相同的分叉点。基数树会把整个提示词作为一条根路径保存每条请求只需要记住自己从哪个节点开始分叉即可。如果用哈希表保存前缀的KV Cache本质上只能精确匹配整段前缀无法处理“A请求和B请求共享前80%的token后20%不同”这种部分匹配的情况。基数树天然支持任意粒度的前缀复用。# 伪代码示意在Radix Tree上查找最长匹配前缀 def match_prefix(req_tokens, tree): node tree.root matched 0 while matched len(req_tokens): child node.find_child_starting_with(req_tokens[matched]) if child is None: break common child.longest_common_prefix(req_tokens[matched:]) if common 0: break matched common node child return node, matched这条查找路径的时间复杂度近似O(P)P为匹配到的前缀长度。对LLM推理来说每个token的匹配只需要做一次查表开销可以忽略不计。3.2 缓存替换策略与生命周期管理缓存节点不能无限增长。RadixAttention采用类似LRU的思路管理树的规模每个节点会记录引用计数。当一条请求结束时从该请求的最后一个节点开始沿路径回溯引用计数减一。只有引用计数归零的节点才会被释放。这个机制的妙处在于它把缓存的生命周期和实际请求的引用关系绑定在一起。如果某个系统提示词被100个并发请求共享它路径上节点的引用计数始终保持在高位那么即使整棵树内存紧张LRU也不会轻易淘汰它。相比之下普通LRU缓存只记录访问时间在高并发共享前缀场景下容易误伤热点数据。3.3 实际收益多轮对话场景的显存与延迟对比我们在一个内部测试场景里验证过收益。用Llama-3.1-8B模型固定一个1000 token的系统提示词模拟20个用户并行发起多轮对话请求。同一批次下SGLang在首字延迟上比未开启前缀缓存的基线降低约62%等效吞吐量提升约2.3倍。显存方面由于前缀KV Cache被复用模型并发数可以开得更大整体显存占用曲线明显更平缓。4. 结构化输出引擎让大模型生成“一定能被解析”的结果前面提到结构化输出这是SGLang另一个核心组件。很多开发者以为结构化输出只是在prompt里多加几个约束词其实真正的实现远比这复杂。4.1 约束解码的实现原理SGLang在做结构化生成时会在解码阶段介入token选择过程。给定一个JSON Schema或正则表达式SGLang会将其编译成一个有限状态机FSM。在生成每一步token之前系统根据当前已生成的token序列查询FSM得到下一个位置允许出现的合法字符集合再根据这个集合构造一个mask把合法的token id筛选出来。# 伪代码基于FSM的受限解码 fsm compile_schema(json_schema) next_allowable_tokens fsm.get_allowable_tokens(prefix_tokens) logits model.forward(prefix_tokens) masked_logits logits.masked_fill(~next_allowable_tokens, float(-inf)) next_token sample(masked_logits, temperature0.2)这种方式的优势是硬约束。模型输出的每一步都被限制在合法集合内最终结果的JSON解析成功率接近100%彻底告别了“生成完再解析失败重试”的循环。4.2 与JSON Mode、Outlines等方案的对比很多框架都提供JSON Mode功能但实现层级不太一样。OpenAI的JSON Mode本质是system prompt层面的软引导模型可能偶尔输出不合法JSON。部分第三方库用constrained decoding实现但只关注输出末尾的格式校验。SGLang的结构化输出引擎和xgrammar这种库的思路更接近把约束直接编译进解码过程在推理层强行保证合法性。如果你已经用了SGLang那么结构化输出可以直接作为内置能力启用不需要额外接Outlines或Jsonformer。我们在实际项目中用这个特性替换掉了之前prompt软引导加后处理校验的方案解析失败率从大约8%降到了0.1%以下。那0.1%还是因为模型提前生成了EOS token导致空结果而不是格式错误。4.3 结构化输出与采样参数的配合这里有个实际操作层面的细节我踩过坑。结构化输出的约束越强解码空间越小生成内容越容易陷入重复。所以使用结构化输出时不建议把temperature设得太高或太低。我们测试下来temperature在0.2到0.5之间比较合适。设成0容易退化成模式化文本设成大于0.8则可能在合法集合内做无意义抖动生成一些语义偏离的内容。5. SGLang与vLLM的正面硬刚吞吐量、延迟、功能侧重点的差异做推理框架选型绕不开的问题是SGLang和vLLM到底怎么选。这两个框架都是当前社区最活跃的LLM推理方案但设计哲学差别很大。5.1 吞吐量与延迟的真实差异vLLM的核心创新是PagedAttention它把KV Cache分割成固定大小的page像操作系统虚拟内存一样按页分配解决的是显存碎片化问题。这带来一个直接好处显存利用率更高可以塞下更大的batch从而提升整体吞吐量。SGLang的RadixAttention解决的是另一个问题——计算复用。它关注的是“同一个前缀被多条请求反复计算”的浪费。两个框架的性能表现因此呈现不同趋势如果请求之间几乎没有任何公共前缀比如纯随机短问题打流vLLM和SGLang的吞吐差距不大甚至vLLM可能略微领先。但在多轮对话、few-shot场景、或者并行采样这些连续请求共享大量公共前缀的任务里SGLang的优势会被放大。我们实测一个20轮长对话的场景SGLang吞吐量比vLLM提高约41%首字延迟降低约22%。维度SGLangvLLM显存管理RadixAttention基数树缓存PagedAttention按页管理核心优化目标减少前缀重复计算提高显存利用率最佳适用场景多轮对话、并行采样、共享长前缀高并发短请求、大batch吞吐结构化输出内置约束解码依赖外部库或prompt软约束多模态支持支持LLaVA等支持部分多模态模型社区热度增长快学术圈用得多生态成熟生产部署案例多5.2 工程成熟度的取舍vLLM毕竟发展时间长它的部署生态更成熟和Kubernetes、监控系统、模型仓库的集成案例更丰富。SGLang虽然性能亮眼但版本迭代速度极快API变化也比较频繁。我刚开始接触SGLang的时候启动服务用的是python -m sglang.launch_server后来版本升级入口变成了sglang.launch_server模块参数也有一些调整。如果你的团队没有专门的推理平台工程师选择SGLang之前要做好持续跟进版本更新的心理准备。5.3 我个人的选型建议我的建议分三种情况如果业务以短请求高并发为主请求之间没有明显公共前缀vLLM是稳妥选择如果业务有大量多轮对话、Agent规划或者并行采样任务SGLang的前缀复用能力会带给你实打实的性能提升如果两者都要可以考虑按路由区分——把多轮问答流量导入SGLang服务短请求走vLLM服务。6. 从零拉起SGLang服务镜像部署、启动命令与显存调优纪要理论聊得差不多了接下来是实战环节。这部分我直接给可复用的部署流程和参数调整经验。6.1 环境准备与镜像部署SGLang的依赖比较重强烈建议直接用官方镜像不要自己手动编译。官方镜像一般发布在lmsysorg/sglang。执行前先确认硬件驱动支持CUDA 12.x镜像内自带的CUDA版本要和你机器的驱动版本兼容。docker run -it --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v /data/models:/models \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path /models/Qwen2.5-7B-Instruct \ --port 30000镜像里默认工作目录为/sglang-workspace代码可以直接挂载进去方便调试。不要忘了加--shm-size参数。如果不加Docker默认共享内存只有64MB数据处理稍微大一点就会出现共享内存不足的错误这个坑我踩过的次数已经记不清了。如果不想用Docker也可以用pip安装。需要注意SGLang的发布节奏偏激进稳定版本和最新特性之间差距较大官方推荐的安装命令里往往带有预发布标记uv pip install --prereleaseallow sglang使用uv而不是pip的原因很简单——SGLang依赖很多C扩展解析依赖时uv比pip快得多失败率也更低。装完之后可以用sglang.check_env检查环境是否完整。6.2 启动参数与显存分配调整启动服务时影响最大的几个参数--mem-fraction-static控制静态显存占用比例默认0.9。这个参数设得太高容易OOM设得太低会频繁做KV Cache的CPU offload导致延迟暴涨。--max-total-token限定KV Cache token总量防止显存超卖。--schedule-conservativeness调度保守性默认是0.0。数值越高调度越保守在高并发场景下可以避免CPU和GPU负载抖动但会增加排队延迟。--cuda-graph-max-batch-size控制CUDA Graph的最大batch。数值越大小请求的图编译开销越低但显存占用也会增加。一般建议设为系统最大并发请求数的70%到90%。6.3 多卡部署方案显存不够跑大模型时SGLang支持tensor parallel、data parallel和pipeline parallel。我们线上用两张A100跑Qwen2.5-72B用的命令是加--tp 2。数据并行--dp适合多个请求完全独立、不需要共享前缀的流量它把不同请求分发到不同GPU上互不干扰。调度器还支持--dp-size与--tp-size组合但配置复杂度会上升建议先用纯TP跑通再根据压测结果决定是否引入DP。7. 实战中遇到的那些坑前缀缓存失败、并发超时与显存碎片最后记录几个我在实际使用SGLang时遇到过的问题和排查思路。这些内容在官方文档里很难一次找全遇到了只能自己摸索。7.1 前缀缓存命中率接近零我第一次把SGLang接入线上服务时发现RadixAttention的命中率低得可怜。排查了半天问题出在我们业务请求里带了当前时间戳字段比如“今天是2025年5月18日请帮我安排行程”。时间戳每次不同导致整条请求前缀全部失配。这个问题的解法有两种一是从prompt中移除动态字段把时间等变量放到共享系统提示词以外的位置二是在前缀长度不符合预期时不要对整条请求做缓存复用而是以更粗粒度的对话边界为准。SGLang提供了自定义分块逻辑的能力把每个请求的缓存ID设置成session级对话内复用依然成立。7.2 并发峰值下的CPU调度瓶颈SGLang的调度器是单进程模型当并发请求数超过150路时CPU侧的调度开销开始变得明显表现为GPU利用率下降但请求排队时间上升。定位方式很简单看/metrics接口里的engine_accept_latency和schedule_queue_length指标。如果排队长度持续增长优先调大--schedule-conservativeness让调度器更积极地批量处理请求实在不行再考虑引入DP缓解单进程压力。7.3 多模态输入导致的首字延迟波动我们后来接入了多模态模型发现带图片的请求首字延迟明显高于纯文本请求。根因是多模态输入的图像tokens数量不稳定导致batch内请求的实际序列长度差异巨大调度器为了兼顾最长序列让其他请求也等了更久。后来我们按照输入类型拆分了服务图像请求单独一个服务实例并给这个实例单独配置了更大的--mem-fraction-static首字延迟才稳定下来。7.4 一个小技巧用结构化输出给共享前缀补充缓存锚点在做Agent类的多步任务时我会故意在每步生成的结尾加上一个固定的分隔符比如把“最终结果”作为固定后缀。这样下一次请求就能以这个分隔符为节点继续复用前缀。这个技巧听起来有点取巧但在实际压测里它让连续策略步骤之间的前缀命中率提升了约35%。以上这些经验是我在SGLang从0.2版本一路用过来沉淀下来的。这个框架还在快速迭代特性更新频繁但它的核心设计思路——前端描述生成流程、运行时管理token级复用、解码阶段注入结构化约束——已经足够成熟也足够解决当前LLM服务里最典型的几个性能痛点。如果你正在被重复计算和格式解析问题困扰建议用一个真实业务场景跑一次对比观察RadixAttention的命中率和整体吞吐曲线再决定要不要切过来。从我的经验看多轮对话场景下这个框架值得一试。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑