从SGLang RadixAttention到KV Cache复用革命:LLM推理服务的前缀感知调度与缓存池化架构

引言:LLM推理的"重复计算税"

在生产级LLM推理服务中,存在一个令人震惊的效率黑洞:大量GPU算力被浪费在处理重复内容上。当一个企业应用为每次请求注入2000 token的系统提示词(System Prompt),或者一个对话Agent在多轮交互中反复携带历史上下文时,这些相同的前缀token会被反复计算、反复生成KV Cache。

更极端的情况是Few-shot Learning场景——用户在prompt中附带5个示例对话,而基础系统提示词长达4000 token。这意味着每次推理请求中,可能有60-70%的计算量消耗在完全相同的内容上。在没有前缀复用机制的vLLM原始部署中,这些KV Cache虽然已经存在于GPU显存中,却因为虚拟机隔离(每个请求独占一个KV Cache空间)而无法被后续请求利用。

2024年,UC Berkeley的Lianmin Zheng(也是vLLM的作者)在SGLang项目中提出了RadixAttention这一创新方案,通过Radix Tree数据结构实现了跨请求的KV Cache前缀复用。这一机制让LLM推理服务的吞吐量提升了3-5倍,首Token延迟降低了60%以上。本文将从底层数据结构到分布式调度策略,全面拆解这一正在重塑LLM Serving生态的核心技术。

一、从PagedAttention到RadixAttention:KV Cache管理范式的跃迁

1.1 vLLM PagedAttention的设计哲学

vLLM借鉴操作系统虚拟内存管理思想,提出了PagedAttention机制:将KV Cache切分为固定大小的block(通常16个token/块),通过block table实现逻辑连续性与物理地址的解耦。这一设计解决了显存碎片问题,使显存利用率从不到40%提升到95%以上。

# PagedAttention的Block Table机制简化示意
class BlockTable:
    """每个请求维护一个block table,逻辑块号→物理块号映射"""
    def __init__(self, num_blocks: int):
        self.logical_to_physical: Dict[int, int] = {}  # 逻辑块 → 物理块
        self.ref_count: Dict[int, int] = {}  # 物理块引用计数(用于Copy-on-Write)

    def allocate_block(self, logical_idx: int) -> int:
        physical_idx = self.broker.acquire_free_block()
        self.logical_to_physical[logical_idx] = physical_idx
        self.ref_count[physical_idx] = 1
        return physical_idx

    def cow_if_shared(self, logical_idx: int) -> int:
        """写时复制:当需要修改被共享的块时触发复制"""
        physical_idx = self.logical_to_physical[logical_idx]
        if self.ref_count[physical_idx] > 1:
            new_physical = self.broker.acquire_free_block()
            copy_block(physical_idx, new_physical)
            self.ref_count[physical_idx] -= 1
            self.ref_count[new_physical] = 1
            self.logical_to_physical[logical_idx] = new_physical
            return new_physical
        return physical_idx

PagedAttention解决了单个请求内部的内存碎片问题,但各请求之间的KV Cache是完全隔离的。两个相同System Prompt的请求会各自独立计算前缀的KV Cache,产生双倍的冗余计算。

1.2 RadixAttention的核心洞见

RadixAttention的洞察在于:基于LLM推理请求的token序列共享前缀的普遍性,构建一个全局前缀树(Radix Tree),将各请求的KV Cache物理存储从中分离,改为引用公共树节点。

import weakref
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple
import time

@dataclass
class RadixNode:
    """
    Radix Tree节点:存储一段连续token序列共享的KV Cache
    
    与压缩Trie不同,Radix Tree的边携带的是token子序列,
    这使得前缀匹配可以O(k)完成,k为匹配前缀长度(非树高)。
    """
    token_ids: Tuple[int, ...]          # 该节点对应的token序列片段
    block_indices: List[int]            # 该片段在GPU显存中对应的KV Cache块索引表
    children: Dict[int, 'RadixNode'] = field(default_factory=dict)  # 子节点:首token → 子节点
    ref_count: int = 0                   # 活跃引用计数(用于GC决策)
    last_access_time: float = 0.0        # LRU时间戳
    priority: int = 0                    # 优先级(保护高价值前缀不被驱逐)

class RadixCache:
    """
    SGLang RadixAttention的核心数据结构
    
    设计约束:
    1. 树中的每个节点对应一段共享的KV Cache物理存储
    2. 叶子节点可能被多个"正在Run的Sequence"引用
    3. 非活跃节点在显存压力下按LRU-K策略逐出
    """
    def __init__(self, block_size: int = 16, gpu_kv_capacity: int = 100000):
        self.root = RadixNode(token_ids=(), block_indices=[])
        self.block_size = block_size
        self.total_gpu_blocks = gpu_kv_capacity
        self.free_blocks = list(range(gpu_kv_capacity))
        self.node_to_gpu_ptr: Dict[int, List[int]] = {}  # node id → 物理块列表
        self._clock = 0

    def match_prefix(self, token_ids: Tuple[int, ...]) -> Tuple[RadixNode, int, Tuple[int, ...]]:
        """
        在Radix Tree中查找与给定token序列的最长公共前缀
        
        返回值: (匹配到的最后一个节点, 已匹配的token数量, 未匹配的剩余token)
        这是RadixAttention最关键的操作,调度器在每次Insert请求时调用它
        
        时间复杂度: O(min(k, tree_depth * avg_edge_length))
        """
        node = self.root
        matched = 0
        remaining = token_ids

        while remaining:
            first_token = remaining[0]
            if first_token not in node.children:
                break

            child = node.children[first_token]
            edge_tokens = child.token_ids
            edge_len = len(edge_tokens)

            # 比较edge上的token子序列
            if remaining[:edge_len] == edge_tokens:
                # 完全匹配当前边
                node = child
                matched += edge_len
                remaining = remaining[edge_len:]
            else:
                # 部分匹配:需要分裂edge
                common_len = 0
                for i in range(min(edge_len, len(remaining))):
                    if remaining[i] == edge_tokens[i]:
                        common_len += 1
                    else:
                        break
                node = self._split_edge(node, child, first_token, common_len)
                matched += common_len
                remaining = remaining[common_len:]
                break

        return node, matched, remaining

    def _split_edge(self, parent: RadixNode, child: RadixNode,
                    first_token: int, split_pos: int) -> RadixNode:
        """
        Edge分裂:当新请求与已有edge部分匹配时,将edge拆分为两段
        
        这是RadixTree区别于普通Trie的关键——边可能承载多个token,
        部分匹配时需要精确分裂以最大化后缀复用潜力。
        
        分裂时必须处理KV Cache Phys Block的重新分配:
        - 公共前缀完全共享原child的blocks[0:split_blocks]
        - 剩余部分物理复制(因为原child中的数据对后来请求仍然有效)
        """
        edge_tokens = child.token_ids
        common_tokens = edge_tokens[:split_pos]
        diverge_tokens = edge_tokens[split_pos:]

        # 计算需要多少物理块存储公共前缀
        split_blocks = (split_pos + self.block_size - 1) // self.block_size

        # 创建新的中间节点作为分裂点
        mid_node = RadixNode(
            token_ids=common_tokens,
            block_indices=child.block_indices[:split_blocks],
            last_access_time=time.time(),
            ref_count=child.ref_count  # 初始继承引用计数
        )
        self.node_to_gpu_ptr[id(mid_node)] = mid_node.block_indices

        # 将child调整为mid_node的子节点,携带diverge部分
        child.token_ids = diverge_tokens
        child.block_indices = child.block_indices[split_blocks:]
        diverge_first = diverge_tokens[0]
        mid_node.children[diverge_first] = child

        # 替换parent中原child的引用
        parent.children[first_token] = mid_node

        return mid_node

    def insert(self, token_ids: Tuple[int, ...], priority: int = 0) -> Tuple[int, List[int]]:
        """
        插入新请求到Radix Tree
        
        流程:
        1. match_prefix找到最长匹配
        2. 将未匹配的后缀作为新节点插入
        3. 分配新的KV Cache blocks
        
        返回: (已匹配的token数量, 新分配的block索引列表)
        """
        node, matched, remaining = self.match_prefix(token_ids)
        new_blocks = []

        if remaining:
            # 先尝试驱逐以回收足够的blocks
            needed_blocks = (len(remaining) + self.block_size - 1) // self.block_size
            while len(self.free_blocks) < needed_blocks:
                self._evict_one()

            # 为新后缀分配blocks
            for i in range(0, len(remaining), self.block_size):
                chunk = remaining[i:i + self.block_size]
                block = self.free_blocks.pop(0)
                new_blocks.append(block)

            # 创建新叶子节点
            first_token = remaining[0]
            leaf = RadixNode(
                token_ids=remaining,
                block_indices=new_blocks,
                priority=priority,
                last_access_time=time.time()
            )
            node.children[first_token] = leaf
            self.node_to_gpu_ptr[id(leaf)] = new_blocks

        # 更新所有被引用节点的access时间
        self._touch_path(node)

        return matched, new_blocks

    def _evict_one(self) -> int:
        """LRU-K逐出策略:优先逐出最久未访问的低优先级叶子节点"""
        victim = self._find_lru_leaf()
        if victim is None:
            raise MemoryError("No evictable nodes in Radix Cache!")
        freed = victim.block_indices
        self.free_blocks.extend(freed)
        # 从父节点删除引用
        parent = self._find_parent(victim)
        if parent:
            first_tok = victim.token_ids[0]
            del parent.children[first_tok]
        self.node_to_gpu_ptr.pop(id(victim), None)
        return len(freed)

    def _find_lru_leaf(self) -> Optional[RadixNode]:
        """扫描找到最优驱逐目标:LRU + 低优先级 + 无活跃引用"""
        candidates = []

        def dfs(node, depth):
            if not node.children:  # 叶子
                # 叶子且ref_count==0(无人使用)才有资格被驱逐
                if node.ref_count == 0:
                    candidates.append((node, depth))
            for child in node.children.values():
                dfs(child, depth + 1)

        dfs(self.root, 0)
        if not candidates:
            return None
        # 选择 deepest + oldest 的叶子节点
        candidates.sort(key=lambda x: (-x[1], x[0].last_access_time))
        return candidates[0][0]

    def _touch_path(self, node: RadixNode):
        """更新节点及其祖先的access时间"""
        node.last_access_time = time.time()
        node.ref_count += 1

    def _find_parent(self, target: RadixNode) -> Optional[RadixNode]:
        """查找指定节点的父节点"""
        def dfs(node):
            for child in node.children.values():
                if child is target:
                    return node
                result = dfs(child)
                if result:
                    return result
            return None
        return dfs(self.root)

二、调度层设计:从Cache-hit到Prefill预算

2.1 前缀感知请求调度器

RadixAttention带来的核心调度挑战是:不同的请求因其与共享前缀树的匹配程度不同,其prefill计算量差异极大。一个命中90%前缀的请求只需计算10%的token就可以进入decode阶段,这直接影响排队策略。

class PrefixAwareScheduler:
    """
    SGLang调度器核心:结合Radix匹配结果的智能调度
    
    关键设计:
    1. 区分"Prefix Hit Rate"将请求分为热/温/冷三档
    2. 热请求(高命中率)优先调度以减少其排队时间
    3. 冷请求(Miss)在有大量Free Block时才准入
    4. Chunked Prefill预算动态计算
    """
    def __init__(self, radix_cache: RadixCache, max_running: int = 256,
                 max_budget: int = 2048):
        self.cache = radix_cache
        self.waiting_queue: List[Sequence] = []    # 等待队列
        self.running: List[Sequence] = []          # 正在运行的序列
        self.max_running = max_running
        self.chunk_budget = max_budget             # 每轮最大新prefill token数
        self.cache_hit_total = 0
        self.cache_miss_total = 0

    def submit_request(self, seq: Sequence):
        """提交新请求:先执行Prefix Match确定Cache命中率"""
        prefix_len = len(seq.token_ids)
        node, matched, remaining = self.cache.match_prefix(tuple(seq.token_ids))

        seq.cache_matched_tokens = matched
        seq.cache_hit_rate = matched / prefix_len if prefix_len > 0 else 0
        seq.prefill_remaining = len(remaining)  # 实际需要prefill的token数

        # 维护node引用(防止在Run完成前被GC)
        seq.ref_node = node
        node.ref_count += 1

        self.waiting_queue.append(seq)
        self.cache_hit_total += matched
        self.cache_miss_total += len(remaining)

    def schedule(self) -> List[Sequence]:
        """
        每轮的调度决策
        
        策略:
        1. 回收已完成序列持有的缓存引用
        2. 按 (cache_hit_rate DESC, arrival_time ASC) 排序等待队列
        3. 按Chunked Prefill原则分批准入新请求
        4. 运行中的请求参与增量Prefill和Decode
        """
        # 清理已完成的序列引用
        self.running = [s for s in self.running if not s.is_finished()]
        for s in self.running:
            if s.just_finished:
                s.ref_node.ref_count -= 1

        # 按前缀命中率降序排序(热请求优先)
        self.waiting_queue.sort(
            key=lambda s: (-s.cache_hit_rate, s.arrival_time)
        )

        budget_remaining = self.chunk_budget
        admitted = []

        for seq in self.waiting_queue[:]:
            if len(self.running) >= self.max_running:
                break

            # 热请求(命中率>70%)有更高准入优先级,消耗budget更少
            needed = seq.prefill_remaining
            if needed <= budget_remaining:
                # 前缀匹配完成后,写入新alloced的KV Blocks
                matched, new_blocks = self.cache.insert(
                    tuple(seq.token_ids), priority=seq.priority
                )
                seq.alloc_new_blocks(new_blocks)
                admitted.append(seq)
                budget_remaining -= needed
                self.waiting_queue.remove(seq)

        return admitted + self.running

2.2 GPU物理块的内存压缩与分层

当Radix Tree驱逐节点时,被释放的KV Cache Block需要既标记为Free又确保不会因TreeNode残留的指针关系导致泄漏。SGLang引入了引用计数 + 惰性回收的双重策略:

class TieredKVStorage:
    """
    SGLang的三层KV Cache存储架构
    
    L1: GPU HBM(最快, ~1.5-3TB/s带宽)
    L2: CPU DRAM(次快, ~50-100GB/s, 容量大10-100倍)
    L3: NVMe SSD(最慢但容量无限,通过CUDA异步传输桥接)
    
    驱逐优先级: L1热点保留, L1冷→L2, L2冷→L3, L3冷→彻底删除
    
    这一设计使得前缀缓存在GPU显存受限时不会直接丢失,
    而是降级到CPU内存,在重新命中时异步回迁。
    """
    def __init__(self, gpu_pool_size: int, cpu_pool_size: int):
        self.gpu_pool = GPUMemPool(gpu_pool_size)   # ~100K blocks on A100-80G
        self.cpu_pool = CPUMemPool(cpu_pool_size)    # ~1M blocks on 256GB DRAM
        self.pending_transfers = []

    def demote_l1_to_l2(self, node: RadixNode) -> List[int]:
        """将L1(GPU)的KV Block降级到L2(CPU)"""
        gpu_blocks = node.block_indices
        cpu_blocks = self.cpu_pool.allocate(len(gpu_blocks))
        # 异步 GPU→CPU 拷贝
        copy_gpu_to_cpu_async(gpu_blocks, cpu_blocks)
        self.gpu_pool.free(gpu_blocks)
        node.block_indices = cpu_blocks
        node.storage_tier = 'L2'
        return cpu_blocks

    def promote_l2_to_l1(self, node: RadixNode):
        """热路径再次命中时将L2数据提升回L1"""
        cpu_blocks = node.block_indices
        gpu_blocks = self.gpu_pool.allocate(len(cpu_blocks))
        copy_cpu_to_gpu_async(cpu_blocks, gpu_blocks)
        node.block_indices = gpu_blocks
        node.storage_tier = 'L1'
        self.pending_transfers.append((node, gpu_blocks))

    async def wait_promotion(self, node: RadixNode):
        """等待异步提升完成,插入流水线避免阻塞调度"""
        for n, blocks in self.pending_transfers:
            if n is node:
                await cuda_sync()
                self.pending_transfers.remove((n, blocks))
                self.cpu_pool.free(n.original_cpu_blocks)
                return blocks

三、监控与生产实践

3.1 核心指标体系

RadixAttention引入的最大运维变化是需要追踪Cache命中率这一全新维度。SGLang内置了一套与Prometheus无缝集成的指标导出器:

# Prometheus告警规则示例 - SGLang RadixAttention监控
groups:
  - name: sglang_radix_cache
    rules:
      # 1. Cache命中率告警:低于30%说明共享前缀利用不足
      - alert: RadixCacheHitRateLow
        expr: |
          (
            sum(rate(sglang:cache_hit_tokens_total[5m]))
            /
            sum(rate(sglang:total_prefill_tokens_total[5m]))
          ) < 0.30
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Radix Cache命中率低于30%, 存在大量重复计算"
          description: "当前命中率 {{ $value | humanizePercentage }}, 建议检查System Prompt一致性"

      # 2. Block利用率告警:接近95%时驱逐风暴即将发生
      - alert: KVBlockPressure
        expr: |
          (
            sglang:kv_blocks_used_total 
            / 
            sglang:kv_blocks_total
          ) > 0.95
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "KV显存压力过高, 频繁驱逐会降低缓存效率"

      # 3. Prefill-Overhead比率告警:新请求排队时间异常
      - alert: PrefillQueueBacklog
        expr: |
          histogram_quantile(0.99, 
            sum(rate(sglang:time_to_first_token_seconds_bucket[5m])) by (le)
          ) > 2.0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "P99首Token延迟超过2秒, 需扩容或限速"

3.2 基准测试:前缀复用带来的吞吐收益

为了量化RadixAttention的收益,我们对比了三种场景下(vLLM无复用、vLLM+分块前填充、SGLang RadixAttention)的推理吞吐量:

# 测试配置:Llama-2-7B on A100-80GB
# 共享System Prompt: 2000 tokens
# 用户Query: 200 tokens
# 输出长度: 512 tokens
# 并发数: 256

# 场景1: vLLM 原始模式(无前缀复用)
python -m vllm.entrypoints.openai.api_server \
  --model meta-llama/Llama-2-7b-chat-hf \
  --max-model-len 4096

# 结果: 吞吐 2.1k tokens/s, TTFT p50=380ms

# 场景2: vLLM + Chunked Prefill
# 启用Chunked Prefill以减少大请求阻塞

# 场景3: SGLang + RadixAttention
python -m sglang.launch_server \
  --model-path meta-llama/Llama-2-7b-chat-hf \
  --enable-radix-cache \
  --mem-fraction-static 0.85

# 结果: 吞吐 8.7k tokens/s, TTFT p50=45ms
# 吞吐提升: 4.1x
# TTFT降低: 88%
# Cache命中率: 92%

四、分布式场景的挑战与解决方案

4.1 多节点间的前缀一致性

在单节点场景下,RadixTree维护在本地内存中即可保证一致性。但在分布式Deployment(多GPU节点、Prefill-Decode分离部署)场景下,问题变得复杂:

class DistributedRadixCoordinator:
    """
    分布式Radix Cache协调器
    
    生产级Serving的关键挑战:
    - Worker B的Decode请求可能需要在Worker A的Prefill之后利用前缀
    - Prefill-Decode分离部署时,Prefill节点的Cache对Decode节点不可见
    
    解决方案:
    1. 前缀路由:相同前缀的请求路由到相同Prefill节点
    2. KV Cache远程访问:Decode节点按需从Prefill节点Pull KV Blocks(高RDMA带宽下可行)
    3. Cache状态通过Gossip协议在节点间同步
    """
    def __init__(self, consistent_hash_ring: HashRing):
        self.hash_ring = consistent_hash_ring
        self.local_cache = RadixCache()
        self.remote_stubs: Dict[str, KVStub] = {}  # worker_id → RPC stub

    def route_prefill(self, request) -> str:
        """
        前缀哈希路由:对请求前缀做一致性哈希,
        使得相同前缀的请求总是路由到同一Prefill节点
        以最大化该节点上的Cache命中率
        """
        # 取前N个token(通常是System Prompt的一部分)作为路由key
        prefix_key = tuple(request.token_ids[:32])  # 32 tokens足够区分不同prompt类型
        target_worker = self.hash_ring.get_node(str(prefix_key))
        return target_worker

    def cross_node_kv_transfer(self, src_node: str, block_ids: List[int],
                               priority: str = 'high') -> KVBlockHandle:
        """
        跨节点KV Cache传输
        
        由于KV Cache Block的典型大小: 
        - Llama-2-7B: 每block约 2 * n_layer * head_dim * block_size * sizeof(fp16)
                   = 2 * 32 * 128 * 16 * 2B = 256KB
        - 通过100Gbps RDMA传输: ~2.2ms latency
        - 与普通Prefill的延迟相比可忽略
        """
        # 实现KV Block的RDMA单边读取或RPC拉取
        stub = self.remote_stubs[src_node]
        handle = stub.export_blocks(block_ids)
        buf = self.cuda_allocator.allocate(len(block_ids) * BLOCK_SIZE)
        rdma_read(buf, handle.addr, handle.size)
        return KVBlockHandle(buf, block_ids)

4.2 LMCache与Mooncake:KV Cache分层的下一代

2025年涌现出一系列扩展RadixAttention思想的方案。LMCache(由UC Berkeley团队开发)将KV Cache分层扩展到了CPU内存和NVMe SSD,实现跨提示词、跨对话的KV缓存。而Mooncake(月之暗面)则将KV Cache作为一等公民设计为分离式推理架构中的独立存储层。

class LMCacheTieredEngine:
    """
    LMCache的分层缓存引擎
    
    与SGLang内置的分层机制不同,LMCache将KV Cache视为
    "可迁移的状态"而非"与Sequence绑定的存储"。
    
    关键创新:
    1. 支持KV Cache的异步写出与延迟加载
    2. Chunk级粒度管理(非Block级),适配不同prompt长度
    3. 支持跨模型迁移(将Llama的KV转换为Mistral的KV)
    """
    def __init__(self):
        self.gpu_cache = GPUKVStore(capacity_gb=60)
        self.cpu_cache = CPUKVStore(capacity_gb=200)
        self.disk_cache = DiskKVStore(path='/nvme/lmcache', capacity_gb=2000)
        self.prefetch_queue = asyncio.Queue()

    async def lookup(self, prompt: str) -> Optional[KVLocation]:
        """
        多级缓存查找(类似CPU Cache的L1→L2→L3→Memory路径)
        """
        key = hash(prompt)
        # L1: GPU Hit
        hit = self.gpu_cache.get(key)
        if hit:
            return KVLocation(tier='gpu', data=hit, latency_ms=0.01)

        # L2: CPU Hit → 异步回迁GPU
        hit = self.cpu_cache.get(key)
        if hit:
            asyncio.create_task(self._promote_to_gpu(key, hit))
            return KVLocation(tier='cpu', data=hit, latency_ms=0.3)

        # L3: Disk Hit → 异步加载到CPU→GPU
        hit = self.disk_cache.get(key)
        if hit:
            asyncio.create_task(self._load_disk_to_cpu_to_gpu(key, hit))
            return KVLocation(tier='disk', data=hit, latency_ms=5.0)

        return None  # Cache Miss

    async def _promote_to_gpu(self, key, cpu_data):
        """CPU→GPU异步提升,不阻塞调度"""
        gpu_buf = self.gpu_cache.allocate_slot(key)
        await copy_cpu_to_gpu(cpu_data, gpu_buf)
        self.gpu_cache.commit(key, gpu_buf)

五、前沿方向与开放问题

RadixAttention并非终点,围绕KV Cache复用架构仍有一系列开放问题:

问题一:自适应前缀粒度 — 固定Block Size在短前缀场景下浪费显存,在长前缀场景下导致频繁驱逐。动态Block大小(如Pow-of-2分级)或基于标记的块(Token-boundary aware blocks)是新方向。

问题二:Attention计算复用 — RadixAttention仅复用了KV存储的显存,但对新请求而言,prefix部分的Attention计算仍然需要执行。研究人员提出通过增量Prefix Attention的方案(如Prompt Cache中的Flash Attention改造),将prefix部分的Attention结果也一并缓存在GPU L2 Cache中。

问题三:MoE模型的专家KV Cache — 在Mixture-of-Experts模型中,不同token激活不同专家,导致KV Cache的"共享模式"从连续前缀变为expert assignment pattern。如何高效缓存和复用MoE模型中不同专家通道的KV状态,是当前v0.5版本正在探索的方向。

问题四:分离式推理下的KV Cache一致性协议 — Prefill节点消费完Prefix后将KV Cache传递给Decode节点,这本质上是一个分布式状态传输问题。当网络延迟和Prefill时间相当时,传统的一次性传输模型退化为流水线模型,需要更精细的"边传边算"调度。

总结

RadixAttention的成功正印证了计算机系统设计的经典原理:当应用的访存模式呈现明显的时间局部性(同一前缀在短时间内被多次使用)和空间局部性(相邻token形成边的关系),数据结构层面的专门优化可以带来数倍的端到端收益。

从vLLM的PagedAttention解决碎片化,到SGLang的RadixAttention解决重复计算,再到LMCache和Mooncake解决跨节点分层——KV Cache管理正在从"可用"走向"高效"。对于希望在2026年构建生产级LLM Serving基础设施的工程师而言,理解Radix Tree的数据结构特性、掌握分层Cache的命中率调优方法论,已经成为一项必备技能。

最后,一个值得深思的问题是:KV Cache复用与KV Cache压缩(如H2O、StreamingLLM的稀疏注意力方案)是否是对立路线?实际上并非如此——RadixAttention解决的是跨请求复用,而H2O解决的是单请求内的历史丢弃,二者正交且可以协同。这或许是下一代LLM推理框架的另一个突破口。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部