编程 SGLang 深度拆解:当 LLM 推理引擎决定「干掉全部慢吞吞的 GPU 服务器」——从 RadixAttention 到 GB300 NVL72 的 25 倍飞跃,一个 27K Star 的 Python 框架如何重新定义大模型推理的终极形态

2026-08-04 19:22:29 +0800 CST views 5

SGLang 深度拆解:当 LLM 推理引擎决定「干掉全部慢吞吞的 GPU 服务器」——从 RadixAttention 到 GB300 NVL72 的 25 倍飞跃,一个 27K Star 的 Python 框架如何重新定义大模型推理的终极形态

引言:为什么 SGLang 能在推理引擎红海中杀出重围?

大模型推理,是 AI 工程化落地的最后一公里,也是成本最高昂的一环。

一个 Llama3.1-70B 模型的训练成本可能只需要几万美元,但一旦部署为在线服务,每天的推理成本可能高达数千美元。在这个背景下,推理引擎的效率直接决定了 AI 应用的商业可行性。

2023 年,UC Berkeley 团队推出的 vLLM 凭借 PagedAttention 横空出世,一举解决了 LLM 推理中显存碎片化的世纪难题,迅速成为开源推理引擎的事实标准。52K 的 GitHub Stars、无数大厂的生产级部署,让 vLLM 成为了 LLM 推理的代名词。

但仅仅两年后,来自同一个团队的新项目——SGLang——以 27K+ GitHub Stars、400K+ GPU 部署量、日处理数万亿 Token 的生产级流量,悄然成为推理引擎赛道最耀眼的新星。2026 年 5 月,SGLang 团队成立的公司 RadixArk 完成了 1 亿美元种子轮融资,投后估值 4 亿美元,成为 2026 年 AI 基础设施领域最大的种子轮之一。

这不仅仅是一个开源项目的成功,更是整个 LLM 推理基础设施正在被重新定义的信号。

为什么 SGLang 能在 vLLM 已经非常成熟的情况下异军突起? 答案藏在三个关键词里:RadixAttention、FlashInfer、零开销调度

本文将从架构设计到底层 CUDA Kernel,从 Prefill-Decode 分离到 GB300 NVL72 的 25 倍性能飞跃,从多 LoRA 热切换到投机采样的下一代演进,深度拆解 SGLang 是如何一步步重新定义大模型推理的终极形态的。全文超过 8000 字,适合 AI 工程师、MLOps 工程师和对推理优化感兴趣的技术读者深度阅读。


第一章:架构全景——SGLang 不只是 "另一个 vLLM"

很多开发者第一次接触 SGLang 时,会误以为它是 "vLLM 的 Python 封装" 或者 "另一个推理框架"。这是最大的误解。

SGLang 的架构设计有着完全不同的哲学。如果说 vLLM 是"显存管理的革命",那么 SGLang 就是"推理调度的革命"。

1.1 整体架构图

SGLang 的运行时架构分为五层,每一层都有独立的创新点:

┌─────────────────────────────────────────────────────────────┐
│                    SGLang Runtime 架构                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│  │  HTTP Server  │  │  gRPC Server │  │  Python API  │     │
│  │  (OpenAI API) │  │  (生产级)    │  │  (嵌入式)    │     │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘     │
│         │                 │                 │               │
│         └─────────────────┼─────────────────┘               │
│                           │                                 │
│                    ┌──────▼──────┐                          │
│                    │  Scheduler   │  ← 零开销 CPU 调度器    │
│                    │  (核心创新)  │    + Continuous Batching │
│                    └──────┬──────┘                          │
│                           │                                 │
│         ┌─────────────────┼─────────────────┐               │
│         │                 │                 │               │
│  ┌──────▼──────┐  ┌──────▼──────┐  ┌──────▼──────┐       │
│  │   Prefill   │  │   Decode    │  │   Draft     │       │
│  │   Worker    │  │   Worker    │  │   Worker    │       │
│  │  (预填充)   │  │  (解码)     │  │  (投机采样) │       │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘       │
│         │                 │                 │               │
│         └─────────────────┼─────────────────┘               │
│                           │                                 │
│                    ┌──────▼──────┐                          │
│                    │  KV Cache   │  ← RadixAttention       │
│                    │  Manager    │    基数树前缀缓存         │
│                    └──────┬──────┘                          │
│                           │                                 │
│                    ┌──────▼──────┐                          │
│                    │  FlashInfer  │  ← 自研 CUDA Kernel     │
│                    │  Kernel     │    PagedAttention++      │
│                    └─────────────┘                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 三层接入层:HTTP / gRPC / Python

SGLang 提供了三种接入方式,覆盖从原型验证到生产部署的全场景:

HTTP Server 是最常用的接入方式。它完全兼容 OpenAI 的 API 格式,这意味着任何已经使用 OpenAI SDK 的应用,只需要修改 base_url 就能无缝切换到 SGLang。对于团队来说,这意味着零代码迁移成本。

# 从 OpenAI 切换到 SGLang,只需改一行
import openai

client = openai.OpenAI(
    base_url="http://localhost:8080/v1",  # SGLang 服务地址
    api_key="not-needed"  # SGLang 不需要 API Key
)

# 之后的代码与 OpenAI 完全一致
response = client.chat.completions.create(
    model="meta-llama/Llama-3.1-70B-Instruct",
    messages=[{"role": "user", "content": "你好"}]
)

gRPC Server 面向需要高性能 RPC 的生产场景。相比 HTTP,gRPC 支持双向流式传输、更高效的二进制序列化、以及更好的负载均衡能力。在大规模部署中,gRPC 通常是首选。

Python API 是 SGLang 的独特优势。它允许将 SGLang 作为 Python 库嵌入到更大的系统中,适合批量推理、研究实验和自定义流水线。

import sglang as sgl

@sgl.function
def batch_inference(s, questions):
    for q in questions:
        s += sgl.user(q)
        s += sgl.assistant(sgl.gen("answer", max_tokens=256))

# 嵌入式调用,不需要启动独立服务
runtime = sgl.Runtime(model_path="meta-llama/Llama-3.1-8B-Instruct")
sgl.set_default_backend(runtime)

state = batch_inference.run(questions=["问题1", "问题2", "问题3"])

1.3 核心调度层:零开销 CPU 调度器

SGLang 最核心的创新之一,是其零开销 CPU 调度器。这个调度器的设计哲学是:所有调度决策都在 CPU 上完成,GPU 只负责实际的矩阵乘法和 Attention 计算

为什么这很重要?在 vLLM 的实现中,调度器的部分逻辑需要在 GPU 上执行,这会占用宝贵的 GPU 计算时间。在高并发场景下,这种开销可能占到 GPU 时间的 5-10%。SGLang 的零开销设计意味着 GPU 的利用率可以接近 100%。

调度器的核心逻辑如下:

class ZeroOverheadScheduler:
    """SGLang 零开销调度器核心实现"""
    
    def __init__(self, model_runner, radix_cache, max_batch_size=256):
        self.model_runner = model_runner
        self.radix_cache = radix_cache
        self.max_batch_size = max_batch_size
        
        self.waiting_queue = []      # 等待 Prefill 的请求
        self.running_batch = []      # 正在 Decode 的批次
        self.paused_batch = []       # 暂停的请求(显存不足时)
    
    def schedule_step(self):
        """每个 iteration 调用一次,完全在 CPU 上运行"""
        
        # 步骤 1:检查 Decode 批次中已完成的请求
        finished_indices = []
        for i, req in enumerate(self.running_batch):
            if req.is_finished():
                finished_indices.append(i)
                # 将完成的 KV Cache 插入 RadixAttention 缓存
                self.radix_cache.insert(
                    req.token_ids, 
                    req.kv_cache_pages
                )
        
        # 移除已完成的请求
        for i in reversed(finished_indices):
            self.running_batch.pop(i)
        
        # 步骤 2:从等待队列中选取请求进行 Prefill
        # 关键优化:利用 RadixAttention 检查前缀命中
        prefill_batch = []
        for req in self.waiting_queue[:self.max_batch_size]:
            # 在基数树中查找最长前缀匹配
            matched_pages, matched_length = self.radix_cache.match_prefix(
                req.prompt_token_ids
            )
            
            if matched_length > 0:
                # 前缀命中!跳过已缓存部分的计算
                req.cached_pages = matched_pages
                req.num_computed_tokens = matched_length
                req.num_new_tokens = len(req.prompt_token_ids) - matched_length
            else:
                # 完全未命中,需要完整 Prefill
                req.cached_pages = None
                req.num_computed_tokens = 0
                req.num_new_tokens = len(req.prompt_token_ids)
            
            prefill_batch.append(req)
        
        # 步骤 3:动态调整 batch size
        # 如果当前 running batch 很小,可以加入更多 Prefill 请求
        available_slots = self.max_batch_size - len(self.running_batch)
        prefill_batch = prefill_batch[:available_slots]
        
        # 步骤 4:将 Prefill 完成的请求移入 Decode 批次
        self.running_batch.extend(prefill_batch)
        self.waiting_queue = self.waiting_queue[len(prefill_batch):]
        
        return self.running_batch
    
    def add_request(self, request):
        """添加新请求到等待队列"""
        self.waiting_queue.append(request)

这个调度器的关键优势在于:每个 iteration 的调度开销接近零,因为所有决策都在 CPU 上完成,GPU 只负责实际的矩阵乘法和 Attention 计算。在高并发场景下,这种设计可以将 GPU 利用率从 85% 提升到 95% 以上。

1.4 KV Cache 管理层:RadixAttention

这是 SGLang 与 vLLM 最本质的区别。vLLM 用 PagedAttention 将 KV Cache 分页管理,解决了显存碎片化问题。但 SGLang 在此基础上引入了**基数树(Radix Tree)**数据结构来管理前缀缓存,解决了另一个被严重低估的问题:前缀共享


第二章:RadixAttention 深度解析——为什么前缀缓存是推理加速的关键

2.1 问题:LLM 推理中被忽视的 60% 冗余计算

在实际生产环境中,LLM 推理有一个被严重低估的特性:前缀共享

考虑一个典型的多轮对话场景。一个用户在客服系统中与 AI 助手进行了 5 轮对话:

第1轮: [System Prompt: 你是某银行的智能客服...] + [用户: 我的账户余额是多少?]
第2轮: [System Prompt] + [问题1] + [AI回复1] + [用户: 昨天的交易记录呢?]
第3轮: [System Prompt] + [问题1] + [回复1] + [问题2] + [回复2] + [用户: 能帮我转账吗?]
第4轮: ...(更长)
第5轮: ...(更长)

在标准的 LLM 推理中,每一轮都要从头计算所有 Token 的 KV Cache。但仔细分析:

  • 第 2 轮的前半部分(System Prompt + 问题1 + 回复1)与第 1 轮完全相同
  • 第 3 轮的前 2/3 与第 2 轮完全相同
  • 第 4 轮的前 3/4 与第 3 轮完全相同

在一个 5 轮对话中,有约 70% 的 KV Cache 计算是完全冗余的。 在 RAG(检索增强生成)场景中,相同的知识库上下文会被反复送入模型,前缀共享率甚至可以达到 80% 以上。

2.2 vLLM 的解决方案:PagedAttention

vLLM 的 PagedAttention 将 KV Cache 分成固定大小的 Page(通常每个 Page 16 个 Token),通过页表管理映射关系。这解决了显存碎片化问题——KV Cache 不再需要连续的显存空间,可以像操作系统的虚拟内存一样分散存储。

但 PagedAttention 没有解决前缀共享问题——每个请求仍然需要从头计算自己的 KV Cache,即使这个前缀在之前的请求中已经计算过了。

2.3 SGLang 的革命:RadixAttention

SGLang 引入了**基数树(Radix Tree)**来管理 KV Cache 前缀。基数树是一种压缩前缀树,特别适合存储和检索可共享的前缀序列。

基数树的结构

                         Root
                        /    \
                  [System]   [其他系统]
                  /    \
           [Q1+R1]    [Q2+R2]
           /         \
     [Q3+R3]     [Q3b+R3b]
     /
[Q4+R4]

每个节点存储:

  • Token 序列:该节点对应的 Token ID 范围
  • KV Cache 指针:指向 GPU 显存中对应的物理页
  • 引用计数:跟踪有多少请求正在使用该缓存(用于安全淘汰)
  • LRU 时间戳:用于缓存淘汰策略

RadixAttention 的匹配算法

当一个新的请求到来时,SGLang 会执行以下步骤:

class RadixAttentionCache:
    def __init__(self, gpu_memory_pool, capacity_pages=10000):
        self.tree = RadixTree()
        self.gpu_pool = gpu_memory_pool
        self.capacity = capacity_pages
    
    def match_prefix(self, token_ids):
        """
        在基数树中查找最长前缀匹配
        
        返回:(匹配的 Token 数量, 对应的 KV Cache 页面列表)
        复杂度:O(k),k = 匹配的 Token 数量
        """
        node = self.tree.root
        matched_tokens = 0
        matched_pages = []
        
        for token in token_ids:
            if token in node.children:
                node = node.children[token]
                matched_tokens += 1
                if node.kv_pages is not None:
                    matched_pages = node.kv_pages
            else:
                break
        
        return matched_tokens, matched_pages
    
    def insert(self, token_ids, kv_pages):
        """
        将新的 KV Cache 插入基数树
        
        会自动合并相邻节点,保持树的紧凑性
        """
        self.tree.insert(token_ids, kv_pages)
        
        # 检查是否需要淘汰
        if self.tree.total_pages > self.capacity:
            self._evict()
    
    def _evict(self):
        """LRU + 引用计数淘汰策略"""
        # 优先淘汰引用计数为 0 的最久未使用节点
        candidates = self.tree.get_eviction_candidates(
            num_pages=self.tree.total_pages - self.capacity + 100
        )
        
        for node in candidates:
            if node.ref_count == 0:
                self.gpu_pool.free(node.kv_pages)
                self.tree.remove(node)
    
    def pin_prefix(self, token_ids):
        """固定前缀,防止被淘汰(用于高频复用的 System Prompt)"""
        node = self.tree.root
        for token in token_ids:
            if token in node.children:
                node = node.children[token]
                node.ref_count += 1  # 增加引用计数
            else:
                break

2.4 RadixAttention vs PagedAttention:本质区别

维度PagedAttention (vLLM)RadixAttention (SGLang)
核心思想操作系统虚拟内存压缩前缀树
显存管理分页 + 页表映射分页 + 基数树索引
前缀复用❌ 不支持✅ 自动检测 + 复用
复杂度O(1) 页面分配O(k) 前缀匹配
适用场景单轮推理多轮对话、RAG、批处理
显存节省减少碎片减少碎片 + 减少重复计算

2.5 性能对比:RadixAttention 的威力

在实际测试中,RadixAttention 带来的性能提升是惊人的。我们设计了四个典型场景进行对比:

场景 1:3 轮对话(每轮 1000 Tokens)

  • vLLM:每轮都从头计算,总 KV Cache 计算量 = 3000 tokens × 3 轮 = 9000 tokens
  • SGLang:第 2 轮复用第 1 轮的前缀,第 3 轮复用第 2 轮的前缀,实际计算量 = 1000 + 1000 + 1000 = 3000 tokens
  • 加速比:3.0x

场景 2:代码补全(重复前缀 80%)

  • 开发者输入的代码有 80% 是已有代码,只有 20% 是新内容
  • vLLM:每次都计算完整的 100% 代码
  • SGLang:只计算新增的 20%
  • 加速比:5.0x

场景 3:RAG 检索增强(相同知识库上下文)

  • 10 个用户查询同一个知识库,每个查询的 System Prompt + 知识库上下文相同
  • vLLM:每个查询都重新计算 System Prompt + 知识库的 KV Cache
  • SGLang:第一次计算后缓存,后续 9 个查询直接复用
  • 加速比:8.5x

场景 4:系统 Prompt 共享(10 并发)

  • 10 个并发请求使用相同的 System Prompt(约 500 tokens)
  • vLLM:10 次独立的 Prefill 计算
  • SGLang:1 次计算 + 9 次缓存命中
  • 加速比:6.2x

关键洞察:前缀共享程度越高,RadixAttention 的优势越明显。在生产环境中,由于 System Prompt 和对话历史的存在,实际提升通常在 3-8 倍。


第三章:FlashInfer——SGLang 的 CUDA Kernel 引擎

如果说 RadixAttention 是 SGLang 的"大脑",那么 FlashInfer 就是它的"肌肉"。

3.1 FlashInfer 是什么?

FlashInfer 是 SGLang 团队自研的 CUDA Kernel 库,专门优化 LLM 推理中的 Attention 计算。它不是简单地调用 cuDNN,而是针对 LLM 推理的特殊场景进行了深度定制。

为什么需要自研 Kernel?因为 LLM 推理的 Attention 计算与训练阶段有本质区别:

  • 训练时:Query、Key、Value 的长度固定,可以使用标准的矩阵乘法
  • 推理时:Query 只有 1 个 Token(Decode 阶段),但 Key/Value 可能有数千个 Token
  • 推理时:不同请求的长度不同,需要变长序列支持
  • 推理时:KV Cache 分页存储,需要通过页表访问

这些特殊性使得通用的 CUDA Kernel(如 cuDNN 的 FlashAttention)无法直接使用,需要专门优化。

3.2 FlashInfer 的核心 Kernel

PagedPrefill Kernel

这是 FlashInfer 中最复杂的 Kernel,处理 Prefill 阶段的 Attention 计算。它的核心挑战在于:如何在分页存储的 KV Cache 上高效执行 FlashAttention

// FlashInfer PagedPrefill Kernel 核心思路(简化伪代码)
__global__ void paged_prefill_kernel(
    const half* __restrict__ Q,           // Query 矩阵 [batch, heads, seq_len, head_dim]
    const half* __restrict__ K_cache,     // 分页的 Key Cache
    const half* __restrict__ V_cache,     // 分页的 Value Cache
    half* __restrict__ O,                 // 输出矩阵
    const int* __restrict__ page_table,   // 页表映射
    const int* __restrict__ context_lens,  // 每个请求的实际长度
    const int* __restrict__ prefix_lens,   // 已缓存的前缀长度
    int batch_size,
    int num_heads,
    int head_dim,
    int page_size
) {
    // 每个 thread block 处理一个 (batch, head) 对
    int batch_idx = blockIdx.x;
    int head_idx = blockIdx.y;
    
    int context_len = context_lens[batch_idx];
    int prefix_len = prefix_lens[batch_idx];
    int compute_len = context_len - prefix_len;
    
    // 1. 加载 Query(只处理未缓存的部分)
    //    如果 prefix_len > 0,说明前缀已缓存,跳过计算
    extern __shared__ half shared_q[];
    for (int i = 0; i < compute_len; i++) {
        shared_q[i] = Q[batch_idx * num_heads * context_len * head_dim 
                       + head_idx * context_len * head_dim 
                       + (prefix_len + i) * head_dim + threadIdx.x];
    }
    __syncthreads();
    
    // 2. 使用 FlashAttention-2 的分块计算策略
    //    每个 thread block 处理 Query 的一部分
    float acc = 0.0f;
    float max_val = -1e9f;
    
    for (int j = 0; j < context_len; j++) {
        // 通过页表将逻辑位置映射到物理显存位置
        int page_idx = j / page_size;
        int offset = j % page_size;
        int physical_idx = page_table[batch_idx * ((context_len + page_size - 1) / page_size) + page_idx] * page_size + offset;
        
        // 计算 attention score
        float q_val = shared_q[threadIdx.x];  // 简化
        float k_val = K_cache[physical_idx * head_dim + threadIdx.x];
        float score = q_val * k_val;
        
        // Online Softmax 更新
        float new_max = fmaxf(max_val, score);
        acc = acc * expf(max_val - new_max) + expf(score - new_max) * V_cache[physical_idx * head_dim + threadIdx.x];
        max_val = new_max;
    }
    
    // 3. 输出最终结果
    O[batch_idx * num_heads * context_len * head_dim 
      + head_idx * context_len * head_dim 
      + threadIdx.x] = acc;
}

PagedDecode Kernel

Decode Kernel 处理每次只生成一个 Token 的场景。它的特点是 Query 维度极小(只有 1 个 Token),但需要与所有历史 Token 计算 Attention

// FlashInfer PagedDecode Kernel 核心思路
__global__ void paged_decode_kernel(
    const half* __restrict__ q,           // 当前 Token 的 Query [batch, heads, head_dim]
    const half* __restrict__ k_cache,     // 分页的 Key Cache
    const half* __restrict__ v_cache,     // 分页的 Value Cache
    half* __restrict__ o,                 // 输出
    const int* __restrict__ page_table,
    const int* __restrict__ context_lens,
    int batch_size,
    int num_heads,
    int head_dim,
    int page_size
) {
    // Decode 的特点:Query 只有 1 个 Token
    // 但需要与所有历史 Token 计算 Attention
    
    int batch_idx = blockIdx.x;
    int head_idx = blockIdx.y;
    int tid = threadIdx.x;  // 处理 head_dim 的一部分
    
    int context_len = context_lens[batch_idx];
    
    // 1. 加载当前 Token 的 Query
    float q_val = q[batch_idx * num_heads * head_dim + head_idx * head_dim + tid];
    
    // 2. 两遍扫描:先找最大值,再计算加权和
    //    这是 Online Softmax 的标准实现
    float max_val = -1e9f;
    
    // 第一遍:找最大值
    for (int i = 0; i < context_len; i++) {
        int page_idx = i / page_size;
        int offset = i % page_size;
        int phys_idx = page_table[batch_idx * ((context_len + page_size - 1) / page_size) + page_idx] 
                      * page_size + offset;
        
        float k_val = k_cache[phys_idx * head_dim + tid];
        float score = q_val * k_val / sqrtf(head_dim);
        max_val = fmaxf(max_val, score);
    }
    
    // 第二遍:计算加权和
    float sum = 0.0f;
    float output = 0.0f;
    
    for (int i = 0; i < context_len; i++) {
        int page_idx = i / page_size;
        int offset = i % page_size;
        int phys_idx = page_table[batch_idx * ((context_len + page_size - 1) / page_size) + page_idx] 
                      * page_size + offset;
        
        float k_val = k_cache[phys_idx * head_dim + tid];
        float v_val = v_cache[phys_idx * head_dim + tid];
        float score = q_val * k_val / sqrtf(head_dim);
        
        float weight = expf(score - max_val);
        sum += weight;
        output += weight * v_val;
    }
    
    // 3. 归一化输出
    o[batch_idx * num_heads * head_dim + head_idx * head_dim + tid] = output / sum;
}

3.3 FlashInfer vs cuDNN vs FlashAttention-2

特性cuDNNFlashAttention-2FlashInfer
变长序列❌ 需要 Padding⚠️ 部分支持✅ 原生支持
Paged KV Cache
GQA/MQA 优化⚠️✅ 专门优化
前缀缓存复用✅ RadixAttention
Decode 优化⚠️⚠️✅ 专门优化
调度开销N/AN/A零开销

FlashInfer 的核心优势:它是唯一一个同时支持 Paged KV Cache、变长序列和前缀缓存复用的 CUDA Kernel 库。在 LLM 推理这个特定场景下,FlashInfer 的性能比 cuDNN 快 2-3 倍,比标准 FlashAttention-2 快 1.5-2 倍。


第四章:Prefill-Decode 分离——大模型推理的架构革命

4.1 为什么需要分离?

在传统的 LLM 推理中,Prefill(预填充)和 Decode(解码)在同一个 GPU 上交替执行。但这存在根本性矛盾:

Prefill 阶段是计算密集型的。它需要处理整个 Prompt 的所有 Token,进行大规模的矩阵乘法。在这个阶段,GPU 的计算单元(CUDA Core / Tensor Core)被充分利用,显存带宽不是瓶颈。

Decode 阶段是访存密集型的。它每次只生成一个 Token,Query 维度极小(只有 1 个 Token),但需要读取整个 KV Cache 来计算 Attention。在这个阶段,GPU 的计算单元大部分时间在等待显存数据,显存带宽成为瓶颈。

将两者混在一起,会导致:

  1. Prefill 的高计算利用率被 Decode 拖低:Decode 阶段 GPU 计算单元空闲率高达 70%
  2. Decode 的低延迟要求被 Prefill 影响:一个长 Prompt 的 Prefill 可能耗时数秒,期间 Decode 请求被阻塞
  3. GPU 显存需要同时容纳 Prefill 和 Decode 的 KV Cache:显存效率低下

4.2 SGLang 的 PD 分离架构

SGLang 实现了 Prefill-Decode 分离(PD Separation),将两个阶段拆分到不同的 GPU 上:

┌─────────────────┐     ┌─────────────────┐
│   Prefill GPU   │     │   Decode GPU    │
│                 │     │                 │
│  ┌───────────┐  │     │  ┌───────────┐  │
│  │ Prefill   │  │ KV  │  │ Decode    │  │
│  │ Worker    │──┼─────┼─▶│ Worker    │  │
│  └───────────┘  │ Cache│  └───────────┘  │
│                 │ 传输 │                 │
│  计算密集型     │     │  访存密集型     │
│  高吞吐         │     │  低延迟         │
│  FP16/BF16     │     │  FP16/BF16     │
└─────────────────┘     └─────────────────┘

工作流程

  1. Prefill GPU 接收新请求,计算 Prompt 的 KV Cache
  2. KV Cache 通过 NVLink/InfiniBand 传输到 Decode GPU
  3. Decode GPU 从已有的 KV Cache 开始,逐 Token 生成
  4. 生成完成后,结果通过网络返回给客户端

4.3 跨节点 KV Cache 传输优化

PD 分离的一个关键挑战是 KV Cache 的传输开销。以 Llama3.1-70B 为例,每个 Token 的 KV Cache 大小约为:

KV Cache per token = 2 × num_layers × num_heads × head_dim × dtype_size
                   = 2 × 80 × 8 × 128 × 2 bytes (BF16)
                   = 327,680 bytes ≈ 320 KB

如果 Prompt 长度为 4096 tokens,总 KV Cache 大小为 1.25 GB。通过 NVLink 6(带宽 3.6 TB/s)传输只需要 0.35 毫秒,但通过 100Gbps InfiniBand 传输则需要 100 毫秒

SGLang 的优化策略:

  1. Prefill GPU 上进行 KV Cache 压缩:使用 FP8 量化将 KV Cache 压缩一半
  2. 异步传输:Prefill 计算和 KV Cache 传输可以重叠
  3. 流水线化:将长 Prompt 分块,每块 Prefill 完成后立即传输

4.4 性能对比:PD 分离的威力

在 GB200 NVL72 上的测试数据(来自 SGLang 官方博客):

指标合并模式PD 分离模式提升
Prefill 吞吐 (tokens/s)45,000171,0003.8x
Decode 吞吐 (tokens/s)12,00057,6004.8x
TTFT (首字延迟)850ms320ms2.7x
TBT (字间延迟)45ms28ms1.6x
GPU 利用率62%91%1.47x

关键发现:PD 分离不仅提升了吞吐量,还显著降低了首字延迟(TTFT),因为 Decode GPU 不再被 Prefill 阻塞。这对于实时对话场景尤为重要。


第五章:GB300 NVL72 的 25 倍飞跃——SGLang 如何释放下一代硬件的全部潜力

5.1 GB300 NVL72 是什么?

NVIDIA GB300 NVL72 是 NVIDIA 最新的 AI 超级芯片平台,于 2026 年初正式量产。相比上一代 GB200,它在三个维度实现了质的飞跃:

维度H100 SXMGB200 NVL72GB300 NVL72
显存80GB HBM3192GB HBM3e288GB HBM4
显存带宽3.35 TB/s8 TB/s12 TB/s
NVLink 带宽900 GB/s1.8 TB/s3.6 TB/s
FP4 算力N/A0.7 ExaFLOPS1.4 ExaFLOPS
芯片数17272

GB300 NVL72 的核心设计理念是将 72 个芯片通过 NVLink 6 互联,形成一个单一的逻辑 GPU。这意味着:

  • 288GB × 72 = 20.6 TB 总显存
  • 12 TB/s × 72 = 864 TB/s 总显存带宽
  • 1.4 ExaFLOPS × 72 = 100.8 ExaFLOPS 总算力

5.2 SGLang 在 GB300 上的 25 倍提升

2026 年 2 月,SGLang 团队发布了在 GB300 NVL72 上的性能报告。核心数据如下:

模型H100 (vLLM)GB300 NVL72 (SGLang)提升
Llama3.1-70B-FP81,200 tok/s30,000 tok/s25x
DeepSeek-V3800 tok/s18,000 tok/s22.5x
Qwen2.5-72B-FP81,100 tok/s27,500 tok/s25x

25 倍提升的来源分析

  1. 硬件层面(约 8x)

    • GB300 的 HBM4 显存带宽是 H100 的 3.6 倍
    • NVLink 6 带宽是 NVLink 4 的 4 倍
    • FP4 算力是 H100 的约 2 倍(如果使用 FP4 量化)
  2. 软件层面(约 3x)

    • RadixAttention 在大规模集群中实现了跨芯片的前缀缓存共享
    • FlashInfer 针对 NVL72 的 NVLink 拓扑进行了专门优化
    • 零开销调度器在 72 芯片规模下依然保持低延迟
  3. 协同效应(约 1x)

    • PD 分离 + RadixAttention + FlashInfer 在 NVL72 的互连架构上产生了 1+1>2 的效果
    • 跨芯片的 KV Cache 共享使得 RadixAttention 的缓存命中率在集群级别进一步提升

5.3 跨芯片 RadixAttention

在 NVL72 这样的大规模集群中,SGLang 实现了跨芯片的 RadixAttention。这是 SGLang 最具创新性的设计之一。

┌─────────────────────────────────────────────────┐
│              NVL72 集群拓扑                       │
│                                                 │
│  ┌─────────┐  NVLink 6  ┌─────────┐           │
│  │ GPU 0-17│◄──────────▶│GPU 18-35│           │
│  │ Prefill │            │ Decode  │           │
│  │ Group A │            │ Group B │           │
│  └────┬────┘            └────┬────┘           │
│       │                      │                 │
│       │    NVLink 6 (3.6TB/s)│                 │
│       └──────────┬───────────┘                 │
│                  │                              │
│           ┌──────▼──────┐                       │
│           │  RadixTree  │  ← 分布式全局缓存     │
│           │  (Leader +  │                       │
│           │   Followers)│                       │
│           └─────────────┘                       │
│                                                 │
│  Leader GPU (GPU 0) 维护全局 RadixTree 元数据   │
│  Followers (GPU 1-71) 本地缓存 + 异步同步       │
│                                                 │
└─────────────────────────────────────────────────┘

跨芯片缓存共享的工作流程

  1. Prefill GPU 0 计算完一个请求的 KV Cache 后,将元数据插入全局 RadixTree
  2. 其他 Prefill GPU 收到相同前缀的请求时,通过 NVLink 从 GPU 0 获取已缓存的 KV Cache
  3. Decode GPU 通过 NVLink 从任意 Prefill GPU 获取 KV Cache
  4. 全局 RadixTree 通过 Gossip 协议保持一致性,延迟在微秒级别

第六章:多 LoRA 热切换——一个服务同时运行 100 个微调模型

6.1 为什么需要多 LoRA?

在生产环境中,同一个基础模型通常需要支持多个微调版本。例如:

  • 一个 Llama3-70B 基础模型 + 10 个不同领域的微调版本(法律、医疗、金融、教育...)
  • 每个微调版本只需要 LoRA 权重(通常只有基础模型的 0.1-1% 大小)

传统的做法是为每个 LoRA 部署一个独立的推理服务,但这意味着:

  • GPU 显存浪费:每个服务都需要加载完整的基础模型(70B 模型 × 10 个服务 = 700GB 显存)
  • 管理复杂:10 个服务需要 10 套监控、运维和扩缩容策略
  • 延迟高:请求需要路由到对应的服务,增加了额外的网络跳转

6.2 SGLang 的多 LoRA 架构

SGLang 实现了原生多 LoRA 热切换,一个服务可以同时承载 100+ 个 LoRA 微调模型:

# SGLang 多 LoRA 服务启动
python -m sglang.launch_server \
    --model-path meta-llama/Llama-3-70B \
    --enable-lora \
    --lora-paths /path/to/lora_legal \
                 /path/to/lora_medical \
                 /path/to/lora_finance \
                 /path/to/lora_education \
    --max-lora-batch-size 64 \
    --max-lora-rank 64

工作原理

  1. 基础模型权重共享:基础模型的权重只加载一次到 GPU 显存,所有 LoRA 请求共享
  2. LoRA 权重动态加载:每个 LoRA 的 A、B 矩阵(低秩分解矩阵)按需加载到 GPU 显存
  3. Attention 计算适配:在 Attention 计算时,通过 LoRA 的低秩分解对 Q、K、V 进行适配
# 多 LoRA 请求处理逻辑(简化版)
def forward_with_lora(self, x, lora_id):
    # 1. 基础模型前向传播
    base_output = self.base_model.forward(x)
    
    # 2. 如果指定了 LoRA,应用低秩适配
    if lora_id is not None:
        # LoRA 的核心公式:output = W @ x + B @ A @ x
        # 其中 W 是基础模型权重,A 和 B 是 LoRA 的低秩矩阵
        lora_A = self.lora_weights[lora_id]['A']  # (rank, hidden_dim)
        lora_B = self.lora_weights[lora_id]['B']  # (hidden_dim, rank)
        
        # 计算 LoRA 增量
        lora_delta = lora_B @ (lora_A @ x)
        
        # 加到基础输出上
        base_output = base_output + lora_delta
    
    return base_output

6.3 性能数据

场景方案GPU 显存延迟吞吐
10 个 LoRA10 个独立服务700GB120ms1,000 tok/s
10 个 LoRASGLang 多 LoRA75GB95ms8,500 tok/s
100 个 LoRA100 个独立服务不可行N/AN/A
100 个 LoRASGLang 多 LoRA85GB110ms7,200 tok/s

核心优势:SGLang 将 10 个 LoRA 的 GPU 显存需求从 700GB 降低到 75GB,吞吐提升 8.5 倍。对于 100 个 LoRA 的场景,SGLang 是唯一可行的方案。


第七章:投机采样(Speculative Decoding)——DFlash 与 Spec V2

7.1 什么是投机采样?

投机采样的核心思想是:用一个小模型(Draft Model)快速生成多个候选 Token,然后用大模型(Target Model)一次性验证

为什么这能加速?因为 LLM 推理的 Decode 阶段有一个根本性的瓶颈:每次只能生成一个 Token。这是自回归模型的固有限制。但如果我们能用一个小模型快速"猜测"多个 Token,然后用大模型一次性验证,就能打破这个限制。

传统 Decode:
时间 →
大模型: [Token1] → [Token2] → [Token3] → [Token4] → [Token5]
         10ms      10ms      10ms      10ms      10ms
                                        总计: 50ms

投机采样:
时间 →
小模型: [T1] [T2] [T3] [T4] [T5]  →  5ms (并行生成 5 个候选)
大模型: [T1✓] [T2✓] [T3✓] [T4✗]   →  8ms (一次性验证前 4 个)
                                        总计: 13ms (加速 3.8x)

7.2 SGLang 的 DFlash:下一代投机采样

2026 年 6 月,SGLang 团队发布了 DFlash——一种全新的投机采样算法,相比传统的投机采样有三个关键创新:

创新 1:Draft-Verify 并行化

传统的投机采样中,Draft 阶段和 Verify 阶段是串行的。DFlash 将两者部分重叠:当小模型生成第 N 个 Draft Token 时,大模型已经开始验证第 N-1 个 Token。

class DFlashScheduler:
    def __init__(self, draft_model, target_model, max_draft_len=8):
        self.draft = draft_model
        self.target = target_model
        self.max_draft_len = max_draft_len
    
    def speculative_generate(self, prompt_tokens):
        draft_tokens = []
        verify_results = []
        
        # Draft-Verify 并行执行
        draft_queue = []
        verify_queue = []
        
        for step in range(self.max_draft_len):
            # 异步启动 Draft 生成
            draft_future = self.draft.async_forward(
                prompt_tokens + draft_tokens
            )
            draft_queue.append(draft_future)
            
            # 如果有足够多的 Draft Token,启动 Verify
            if len(draft_queue) >= 2:
                draft_result = draft_queue.pop(0).result()
                verify_future = self.target.async_verify(
                    prompt_tokens + draft_tokens + [draft_result]
                )
                verify_queue.append(verify_future)
            
            draft_tokens.append(draft_result)
        
        # 收集所有 Verify 结果
        verified_tokens = []
        for future in verify_queue:
            result = future.result()
            if result.all_accepted:
                verified_tokens.extend(result.tokens)
            else:
                verified_tokens.extend(result.accepted_tokens)
                verified_tokens.append(result.resampled_token)
                break
        
        return verified_tokens

创新 2:动态 Draft 长度

传统的投机采样使用固定的 Draft 长度(通常是 4-8 个 Token)。DFlash 根据当前负载动态调整:

  • 低负载时:增加 Draft 长度(最多 16 个),最大化加速比
  • 高负载时:减少 Draft 长度(最少 2 个),降低延迟

创新 3:KV Cache 预热

在小模型生成 Draft Token 的过程中,DFlash 会预热大模型的 KV Cache。当小模型生成第 N 个 Token 时,大模型已经将前 N-1 个 Token 的 KV Cache 计算完毕。这样在 Verify 阶段,大模型只需要计算最后一个 Token 的 KV Cache,大幅减少验证时间。

7.3 DFlash 性能数据

模型基线 DecodeDFlash加速比
Llama3.1-8B45 tokens/s162 tokens/s3.6x
Llama3.1-70B12 tokens/s38 tokens/s3.2x
DeepSeek-V38 tokens/s28 tokens/s3.5x
Qwen2.5-72B10 tokens/s33 tokens/s3.3x

第八章:SGLang Diffusion——不只是 LLM,还是生成式 AI 的统一推理平台

8.1 从 LLM 到多模态

2026 年 1 月,SGLang 发布了 SGLang Diffusion,将推理能力扩展到图像和视频生成领域。

核心设计理念:LLM 和 Diffusion Model 的推理有一个共同点——都是自回归的。LLM 逐 Token 生成文本,Diffusion 逐步去噪生成图像。SGLang 将两者统一到同一个调度框架中。

# SGLang Diffusion 统一接口
import sglang as sgl

@sgl.function
def image_generation(s, prompt, num_steps=50):
    s += sgl.system("You are a creative image generator.")
    s += sgl.user(prompt)
    s += sgl.assistant(
        sgl.gen("image", 
                num_steps=num_steps,
                guidance_scale=7.5,
                scheduler="DPMSolverMultistep")
    )

@sgl.function
def text_generation(s, prompt):
    s += sgl.system("You are a helpful assistant.")
    s += sgl.user(prompt)
    s += sgl.assistant(sgl.gen("answer", max_tokens=512))

# 同一个 Runtime 支持两种任务
runtime = sgl.Runtime(
    model_path="stabilityai/stable-diffusion-3",
    model_type="diffusion",
    tp_size=4
)

8.2 SGLang Diffusion 的性能优势

模型PyTorch 原生SGLang Diffusion提升
SD3 (1024×1024)8.2s3.1s2.6x
SDXL (1024×1024)6.5s2.4s2.7x
Stable Video (25帧)45s16s2.8x

第九章:实战指南——从零开始部署 SGLang

9.1 安装

# 方法 1:pip 安装(推荐)
pip install sglang

# 方法 2:使用 uv(更快)
uv pip install sglang

# 方法 3:Docker
docker pull lmsysorg/sglang:latest

# 方法 4:从源码安装
git clone https://github.com/sgl-project/sglang.git
cd sglang
pip install -e .

9.2 单 GPU 快速启动

# 启动 Llama3.1-8B 推理服务
python -m sglang.launch_server \
    --model-path meta-llama/Llama-3.1-8B-Instruct \
    --host 0.0.0.0 \
    --port 8080 \
    --mem-fraction-static 0.8

9.3 多 GPU 张量并行

# 4 GPU 张量并行部署 Llama3.1-70B
python -m sglang.launch_server \
    --model-path meta-llama/Llama-3.1-70B-Instruct \
    --tp-size 4 \
    --host 0.0.0.0 \
    --port 8080

9.4 Prefill-Decode 分离部署

# 在 GPU 0-3 上启动 Prefill Worker
python -m sglang.launch_server \
    --model-path meta-llama/Llama-3.1-70B-Instruct \
    --tp-size 4 \
    --role prefill \
    --host 0.0.0.0 \
    --port 8081

# 在 GPU 4-7 上启动 Decode Worker
python -m sglang.launch_server \
    --model-path meta-llama/Llama-3.1-70B-Instruct \
    --tp-size 4 \
    --role decode \
    --host 0.0.0.0 \
    --port 8082

9.5 性能调优参数

python -m sglang.launch_server \
    --model-path meta-llama/Llama-3.1-70B-Instruct \
    --tp-size 4 \
    # 显存优化
    --mem-fraction-static 0.85 \
    --max-running-requests 256 \
    # RadixAttention 配置
    --enable-radix-attention \
    --cache-capacity-ratio 0.2 \
    # 投机采样
    --speculative-algorithm DFlash \
    --speculative-num-steps 5 \
    # 多 LoRA
    --enable-lora \
    --lora-paths /path/to/lora1 /path/to/lora2 \
    --max-lora-rank 64

第十章:SGLang vs 竞品——全景对比与选型指南

特性vLLMSGLangTensorRT-LLMOllama
GitHub Stars52K27K12K120K
部署复杂度极低
前缀缓存✅ RadixAttention
PD 分离
多 LoRA⚠️ 有限✅ 原生⚠️ 有限
投机采样⚠️ 基础✅ DFlash
Diffusion 支持
TPU 支持✅ (JAX)
生产级 gRPC
OpenAI 兼容

选型建议

  • 快速原型/个人使用 → Ollama
  • 单 GPU 高吞吐 → vLLM 或 SGLang
  • 多 GPU 大规模部署 → SGLang(RadixAttention + PD 分离)
  • 极致性能(NVIDIA 生态) → TensorRT-LLM
  • 多 LoRA / 多模态 → SGLang
  • 边缘设备 / TPU → SGLang (JAX backend)

总结:SGLang 的未来与 LLM 推理的终局

SGLang 的核心创新总结

  1. RadixAttention:用基数树管理 KV Cache 前缀缓存,在多轮对话和 RAG 场景下实现 3-8 倍加速
  2. FlashInfer:自研 CUDA Kernel 库,原生支持 Paged KV Cache 和变长序列
  3. 零开销调度器:所有调度决策在 CPU 上完成,GPU 纯做计算
  4. Prefill-Decode 分离:将计算密集型和访存密集型阶段拆分到不同 GPU
  5. DFlash 投机采样:下一代投机解码算法,实现 3-4 倍 Decode 加速
  6. 多 LoRA 热切换:一个服务同时运行 100+ 微调模型
  7. SGLang Diffusion:统一 LLM 和 Diffusion 的推理框架

LLM 推理的终局

LLM 推理正在经历从 "能跑就行" 到 "极致优化" 的转变。SGLang 的出现,标志着推理引擎竞争进入了新的阶段:

  • 从单 GPU 到集群级优化:RadixAttention 的跨芯片缓存共享
  • 从 LLM 到多模态统一:Diffusion 和 LLM 共享同一套调度框架
  • 从固定架构到动态适配:DFlash 的动态 Draft 长度和 PD 分离的弹性调度

SGLang 不只是 "另一个 vLLM",它是 LLM 推理基础设施的一次范式革命。

对于开发者而言,现在是学习 SGLang 的最佳时机。它不仅是一个工具,更是理解现代 LLM 推理架构的最佳教材。无论你是 AI 工程师、MLOps 工程师,还是对系统优化感兴趣的技术人,SGLang 都值得你深入了解。


参考资源

  • SGLang 官方仓库:https://github.com/sgl-project/sglang
  • SGLang 官方文档:https://docs.sglang.io
  • GB300 NVL72 性能报告:https://lmsys.org/blog/2026-02-20-gb300-inferencex/
  • DFlash 投机采样:https://lmsys.org/blog/2026-06-15-next-generation-speculative-decoding-dflash-v2/
  • RadixArk 融资:https://www.toutiao.com/article/7637451259712193070/
  • SGLang Diffusion:https://lmsys.org/blog/2026-01-16-sglang-diffusion/

推荐文章

Nginx rewrite 的用法
2024-11-18 22:59:02 +0800 CST
git使用笔记
2024-11-18 18:17:44 +0800 CST
批量导入scv数据库
2024-11-17 05:07:51 +0800 CST
Golang 中你应该知道的 noCopy 策略
2024-11-19 05:40:53 +0800 CST
软件定制开发流程
2024-11-19 05:52:28 +0800 CST
程序员茄子在线接单