大型语言模型(LLM)越来越多地用于需要多次生成调用、高级提示技术、控制流和结构化输入/输出的复杂任务。 然而,目前还缺乏用于编程和执行这些应用的高效系统。 SGLang 是一种新推出的系统,旨在通过提供复杂语言模型程序的高效执行来解决这一问题。 SGLang 由前端语言和运行时组成。 前端语言通过用于生成和并行性控制的基元简化了编程,而运行时则通过新颖的优化加速了执行,如用于KV缓存重用的RadixAttention和用于更快结构化输出解码的压缩有限状态机。 实验证明,与最先进的推理系统相比,SGLang 在各种大型语言和多模态模型上的吞吐量最多可提高 6.4 倍,可处理的任务包括代理控制、逻辑推理、少量学习基准、JSON 解码、检索增强生成流水线和多轮聊天。
LLM 功能的最新进展扩大了它们的用途,使它们能够处理更广泛的一般任务,并发挥自主代理的功能。 在这些应用中,LLM 参与多轮规划、推理以及与外部环境的交互。 这可以通过工具使用、多种输入模式和各种提示技术(如少量学习、自我一致性、思维骨架和思维树)来实现。 这些新的使用案例需要多次(通常是依赖性的)LLM 生成调用,表明了使用多调用结构完成复杂任务的趋势。
这种转变标志着 LLM 从简单的聊天过渡到更复杂的程序化使用,即程序安排和控制 LLM 的生成过程。 这些程序被称为 “语言模型程序”(LM Programs)。 高级提示技术和代理工作流程都属于 LM 程序的范畴。 LM 程序有两个共同特性:(1) LM 程序通常涉及多个 LLM 调用,中间穿插控制流,以完成复杂任务并提高整体质量。 (2) LM 程序接收结构化的输入并产生结构化的输出,这使得 LM 程序的组成和与现有软件系统的集成成为可能。
在本文中,我们将深入研究 SGLang 框架,探索其架构,分析其性能,并将其与最先进的框架进行比较。 让我们开始吧。
SGLang 简介
尽管 LM 程序被广泛使用,但目前用于表达和执行这些程序的系统仍然效率低下。 SGLang 确定了与高效使用 LM 程序相关的两个主要挑战:
- 编程复杂性: 由于 LLM 的非确定性,开发 LM 程序既繁琐又困难。 这涉及大量的字符串操作、提示的实验性调整、脆弱的输出解析、处理多种输入模式以及实施并行机制。 这种复杂性甚至大大降低了简单程序的可读性。
- 执行效率低: 由于冗余计算和内存使用,执行 LM 程序的效率很低。 为减少延迟和提高吞吐量而优化的最先进推理引擎缺乏对工作负载的直接了解,导致效率严重低下。 一个明显的例子是键值(KV)缓存的重用,它由可重用的中间张量组成,对于生成推理至关重要。 目前的系统缺乏有效的机制来促进 KV 缓存在多个LLM 调用,导致不必要的计算和内存浪费。 此外,对结构化输出(如 JSON 模式)的限制解码也不是最佳选择,因为现有系统一次只能解码一个标记。
为了应对这些挑战,SGLang 为 LLM 引入了结构化生成语言。 其核心思想是系统地利用 LM 程序中的多调用结构,以实现高效执行。 如下图所示,SGLang 包括两个部分:前端语言和后端运行时。
前端语言简化了 LM 程序的编程,而运行时则加速了程序的执行。 这些部分既可以协同工作以提高性能,也可以独立运行。
SGLang 是一种嵌入 Python 的特定领域语言,提供了生成(如 extend、gen、select)和并行控制(如 fork、join)的原语。 它与 Python 的控制流和库兼容,允许用户使用本地 Python 语法轻松开发高级提示工作流。 SGLang 包括一个解释器和一个编译器。 解释器以流的形式管理提示状态,并将原始操作提交到流中进行异步执行,从而确保对同步和程序内并行性的适当控制。 此外,SGLang 程序还可以进行跟踪和编译,以便进一步优化。SGLang 的运行时提出了几种新颖的优化方法,以加速 LM 程序的执行:
- RadixAttention: 该技术可在多次生成调用中自动重复使用 KV 缓存。 在现有的推理引擎中,请求的 KV 缓存会在处理后被丢弃,从而无法在多次调用中重复使用,并减慢执行速度。 SGLang 在弧度树中维护 KV 缓存的 LRU 缓存,像管理传统缓存一样管理 KV 缓存,并使用弧度树进行高效匹配、插入和驱逐。 这样,运行时就能高效处理各种重用模式。
- 压缩有限状态机: 这种技术可以更快地对结构化输出进行约束解码。 现有的系统只能遵循下一个标记的约束,因此每次只能解码一个标记。 取而代之的是,SGLang 会分析约束条件,并构建一个压缩有限状态机来表示这些约束条件,尽可能将多标记路径压缩为单步路径,从而一次解码多个标记,加快解码速度。
- API 投机执行: 对于纯 API 模型,如OpenAI 的 GPT-4SGLang 引入了 API 投机执行,以优化多调用程序。
利用 SGLang 实现了各种 LLM 应用程序,包括代理控制、逻辑推理、少量学习基准、JSON 解码、检索增强生成管道、多轮聊天和多模态处理。 在英伟达 A10G 和 A100 GPU 上对 Llama-7B/70B、Mistral-8x7B、LLaVA-v1.5-7B(图像)和 LLaVA-NeXT-34B(视频)等模型进行了性能测试。 实验结果表明,与现有的编程和推理系统(包括 Guidance、vLLM 和 LMQL)相比,SGLang 在各种工作负载、模型和硬件设置中的吞吐量最高提高了 6.4 倍。
SGLang: 编程模型和方法
通过一个运行示例介绍了 SGLang 编程模型,描述了其语言基元和执行模式,并概述了运行时优化机会。 该模型通过提供灵活、可组合的基元,简化了多调用工作流中的繁琐操作(如字符串操作、API 调用、约束规范、并行性)。 SGLang 是一种嵌入 Python 的特定领域语言。 下图显示了一个使用分支-求解-合并提示法评估一篇关于图像的文章的程序。
函数multi_dimensional_judge接受三个参数:`s`、`path`和`essay`。s 管理提示状态,path 是图像文件路径,essay 是文章文本。 可以使用+=操作符将新字符串和 SGLang 基元添加到状态 s 中执行。 首先,函数将图片和文章添加到提示中。 然后,它使用 select 检查文章是否与图片相关,并将结果存储在s[“related”]中。 如果相关,则使用 gen 将提示分叉为三个副本,以便从不同维度进行并行评估,并将结果存储在f[“judgment”]中。 接下来,它会合并判断,生成摘要,并给出一个字母等级。 最后,它按照正则表达式约束regex所定义的模式,以 JSON 格式返回结果。 SGLang 极大地简化了这一程序,因为如果使用类似 OpenAI API 的接口,由于需要手动操作字符串和控制并行性,同等程序需要的代码行数将是 OpenAI API 的 2.1 倍。
SGLang 提供了用于控制提示状态、生成和并行性的原语,可与 Python 语法和库配合使用。 以下是这些原语:
gen:调用一个模型来生成,并将结果存储在一个变量中,变量名在第一个参数中指定。 它支持一个 `regex` 参数,用于限制输出遵循正则表达式(例如 JSON 模式)定义的语法。
- 选择: 调用模型从列表中选择概率最高的选项。
- += 或扩展: 在提示符后添加字符串。
- [变量名]: 获取生成结果。
- 分叉 创建提示状态的并行分叉。
- join: 重新加入提示状态。
- 图像和视频 接收图像和视频输入。
执行 SGLang 程序的最简单方法是通过解释器,在解释器中,提示符被视为异步流。 像extend, gen 和 select这样的原语被提交到流中进行异步执行。 这些非阻塞调用允许 Python 代码在不等待生成完成的情况下继续运行,类似于异步启动 CUDA 内核。 每个提示都由后台线程中的流执行器管理,从而实现了程序内部的并行性。 在生成结果准备就绪之前,获取生成结果的过程将被阻塞,以确保正确的同步。 另外,SGLang 程序也可以编译为计算图,并使用图执行器执行,从而实现更多优化。 本文默认使用解释器模式,并在附录 D 中讨论编译器模式的结果。 SGLang 通过自己的 SGLang Runtime (SRT) 支持开放重量模型,也支持 API 模型,如 OpenAI 和人类学模型。
LLMs 的编程系统可分为高级(如 LangChain、DSPy)和低级(如 LMQL、Guidance、SGLang)。 高级系统提供预定义或自动生成的提示,如 DSPy 的提示优化器。 低级系统通常不改变提示语,但允许直接操作提示语和基元。 SGLang 是一种类似于 LMQL 和 Guidance 的低级系统。 下表比较了它们的特点。
SGLang 更注重运行时的效率,它有自己共同设计的运行时,可以进行新颖的优化。 高级语言(如 DSPy)可编译为低级语言(如 SGLang)。 稍后将演示如何将 SGLang 作为后端集成到 DSPy 中,以提高运行效率。
上述示例展示了采用 LRU 驱逐策略的 RadixAttention 在九个时间点上的运行情况,展示了弧度树在响应各种请求时的动态演化。 这些请求包括两个聊天会话、一批少量学习查询和自一致性采样。 每条树边都带有一个标签,表示一个子串或一个标记序列。 节点用颜色编码以反映不同的状态:绿色代表新添加的节点,蓝色代表在时间点期间访问过的缓存节点,红色代表已被驱逐的节点。
步骤 1:半径树最初为空。
步骤 2:服务器处理传入的用户信息 “Hello”,并以 LLM 输出 “Hi “作为回复。 系统提示 “您是一位乐于助人的助手”、用户信息 “您好!”和 LLM 回复 “您好!”被合并到树中,成为一条与新节点相连的边。
步骤 3:一个新的提示到达,服务器在弧度树中找到该提示的前缀(即对话的第一轮),并重新使用其 KV 缓存。 新的一轮作为一个新节点附加到树中。
步骤 4:开始新的聊天会话。 步骤 3 中的节点被分成两个节点,以便两个聊天会话共享系统提示。
步骤 5:第二个聊天会话继续进行。 但是,由于内存限制,必须删除步骤 4 中的一个节点。 新的转折点被添加到步骤 4 的剩余节点之后。
步骤 6:服务器接收一个少量学习查询,对其进行处理并将其插入树中。 根节点被拆分,因为新查询与现有节点不共享任何前缀。
步骤 7:服务器收到一批额外的少量学习查询。 这些查询共享同一组少量示例,因此会从步骤 6 中拆分一个节点以实现共享。
步骤 8:服务器收到来自第一个聊天会话的新消息。 它会驱逐第二个聊天会话中的所有节点,因为它们是最近使用最少的。
步骤 9:服务器会收到为步骤 8 中的节点中的问题采样更多答案的请求,这可能是为了自我一致性提示。 为了给这些请求腾出空间,多个节点被逐出。
本例演示了 RadixAttention 如何根据不同类型的请求动态分配和驱逐节点,确保高效的 KV 缓存重用和内存管理。
SGLang:评估和结果
开放重量模型的结果
延迟和吞吐量结果如下图所示。 SGLang 将吞吐量提高了 6.4 倍,将延迟降低了 3.7 倍。 这些改进得益于 KV 缓存的重复使用、单个程序内并行性的利用以及更快的受限解码。
在这些基准测试中,缓存命中率从 50% 到 99% 不等。 图 13(附录)列出了所有基准的已达到和最佳缓存命中率,表明 SGLang 的缓存感知调度平均接近 96% 的最佳命中率。
在具有张量并行性的大型模型上的结果
在同一组基准测试中,对较大的模型 Mixtral-8x7B 和 Llama-70B 进行了张量并行测试,结果如下图所示。 大型模型的提速趋势与小型模型类似,这表明 SGLang 的优化对大型模型具有良好的普适性。 由于缺乏高效的张量并行实现,Guidance 和 LMQL 被省略。
多模态模型的结果
SGLang 本机支持图像和视频基元的多模态模型。 本文的优化与多模态模型兼容。 对于 RadixAttention,计算输入图像的哈希值并将其用作 radix 树中的密钥,从而允许重复使用来自同一图像的 KV 图像标记缓存。 LLaVA-v1.5-7B (图像)在 llava-bench-in-the-wild 上运行,LLaVA-NeXT-34B(视频)在 ActivityNet 上运行。 由于这些模型没有得到其他基线系统的良好支持,因此将模型作者在 Hugging Face Transformers 中的原始实现作为基线。 如下表所示,SGLang 在这些基准测试中的吞吐量最高提高了 6 倍。 在llava-bench-in-the-wild中,处理了关于同一图像的多个问题,SGLang运行时在这种情况下重复使用了KV缓存。
生产部署
SGLang 已部署在 Chatbot Arena 中,为开放式模型提供服务。 由于某些模型的流量较低,因此每个模型只由一名 SGLang 工作者提供服务。 一个月后,LLaVA-Next-34B 的 RadixAttention 缓存命中率为 52.4%,Vicuna-33B 为 74.1%。 缓存命中率来自常见的系统消息、经常重复使用的示例图像和多轮聊天记录。 这使 Vicuna-33B 的首次标记延迟平均减少了 1.7 倍。
(机器翻译,轻度译后编辑,仅供参考)
编辑:胡跃