DataWhale组队学习笔记--llm-algo-leetcode(五)
文章目录vLLM PagedAttention(vLLM 分页注意力)1. 痛点传统 KV Cache 的内存刺客2. 破局思路引入操作系统的虚拟内存3. PagedAttention 的工作机制4. PagedAttention 带来的核心红利SGLang RadixAttention(SGLang基数注意力)1. 核心痛点重复 Prefill 与跨请求浪费2. 核心原理用基数树Radix Tree自动管理 KV Cache3. 典型应用场景4. 对比PagedAttention vs. RadixAttentionvLLM PagedAttention(vLLM 分页注意力)在大语言模型LLM的部署和推理优化中vLLM 提出的PagedAttention分页注意力机制是一个里程碑式的突破。它直接解决了大模型在生成长文本时显存GPU Memory被严重浪费的核心痛点。以下是 PagedAttention 的核心原理及其实战机制的拆解1. 痛点传统 KV Cache 的内存刺客在自回归Autoregressive生成过程中大模型需要保存之前生成的每个 Token 的 Key 和 Value 张量以避免每次生成新词时重复计算。这些被缓存的数据就叫KV Cache。传统推理框架在管理 KV Cache 时面临严重的内存浪费问题预分配导致内部碎片系统通常会为了以防万一预先为每个请求分配一段连续的、达到模型最大允许长度的显存空间例如 2048 或 4096 tokens。但很多请求实际生成的长度远达不到最大值多出来的显存就被白白占用了。无法高效共享在并行采样如 Beam Search中同一个 Prompt 会产生多个不同的输出分支。传统的连续内存分配无法让这些分支共享 Prompt 阶段的 KV Cache导致一份前缀被复制了多份。据统计传统的连续内存管理机制会导致高达60% - 80%的 KV Cache 显存被浪费。2. 破局思路引入操作系统的虚拟内存vLLM 团队从操作系统的虚拟内存分页机制Virtual Memory Paging中汲取了灵感。在操作系统中程序认为自己拥有一块连续的“逻辑内存”但实际上这些内存在物理上是被切分成一个个固定大小的“页Pages”并可以分散存储在物理内存的各个角落。系统通过“页表Page Table”来记录逻辑地址到物理地址的映射关系。3. PagedAttention 的工作机制PagedAttention 将这种分页思想直接移植到了 LLM 的注意力机制计算中KV BlocksKV 块系统不再为每个请求分配一整段连续的 KV 显存而是将其划分为一个个固定大小的块Block。每个 Block 包含固定数量的 Token 的 KV 向量例如每个 Block 只装 16 个 Token。非连续物理存储这些 Block 在 GPU 的显存中不需要连续存放哪里有空闲空间就分配在哪里。Block Table块表vLLM 维护着一个中心化的块表负责将每个请求连续的“逻辑 KV 块”映射到显存中散落的“物理 KV 块”。当模型进行 Attention 计算时PagedAttention 算法会在底层自动查找块表跨越这些非连续的物理块准确地提取出需要的 KV 向量完成矩阵乘法。这个过程对上层模型是完全透明的。4. PagedAttention 带来的核心红利几乎消除显存浪费因为是按需动态分配用完一个 Block 再分配下一个显存的内部浪费被严格控制在最后一个没有装满的 Block 内通常浪费率低于 4%。吞吐量Throughput翻倍节省下来的海量显存可以用来同时容纳更多并发请求的 KV Cache从而显著增大 Batch Size。在相同的硬件下vLLM 的吞吐量通常能达到传统框架的2 - 4 倍。写时复制Copy-on-Write实现内存共享对于多个请求共享同一个 Prompt或者执行 Beam Search 这种有共同前缀的场景不同请求在 Block Table 中只需指向相同的物理块。只有当它们各自生成不同的后续 Token 时系统才会分配新的物理块写时复制。这使得复杂推理场景下的显存占用呈指数级下降。代码实现importtorchfromtypingimportListclassRequest:def__init__(self,request_id:int,prompt_len:int):self.request_idrequest_id self.seq_lenprompt_len self.block_table:List[int][]classKVCacheManager:def__init__(self,num_blocks:int,block_size:int,head_dim:int):self.num_blocksnum_blocks self.block_sizeblock_size self.head_dimhead_dim# TODO 1: 模拟预分配一块大显存池self.physical_kv_cachetorch.zeros(num_blocks,block_size,head_dim)# 跟踪哪些物理块被占用了self.free_blocks:List[int]list(range(num_blocks))defallocate_for_prefill(self,req:Request): 请求刚进来时 (Prefill阶段)为它的 Prompt 长度分配所需的全部 Block # TODO 2: 计算需要的 block 数量向上取整needed_blocks(req.seq_lenself.block_size-1)//self.block_size# TODO 3: 从 free_blocks 中弹出对应数量的 block 索引iflen(self.free_blocks)needed_blocks:raiseRuntimeError(OOM)for_inrange(needed_blocks):block_idself.free_blocks.pop(0)req.block_table.append(block_id)defallocate_for_decode(self,req:Request): 自回归生成时 (Decode阶段)检查序列长度。 如果当前最后一个 Block 满了则按需分配 1 个新 Block。 req.seq_len1# TODO 4: 判断是否需要新的 Blockis_new_block_needed(req.seq_len%self.block_size)1ifis_new_block_needed:ifnotself.free_blocks:raiseRuntimeError(OOM)block_idself.free_blocks.pop(0)req.block_table.append(block_id)defget_physical_cache(self,req:Request)-torch.Tensor: 根据块表把不连续的物理块拼凑成逻辑上连续的 KV Cache # TODO 5: 根据 req.block_table 的索引从物理池中提取对应的块blocks[self.physical_kv_cache[block_id]forblock_idinreq.block_table]cat_blockstorch.cat(blocks,dim0)# 只截取真实 seq_len 长度返回returncat_blocks[:req.seq_len]# 运行此单元格以测试你的实现deftest_paged_attention_manager():try:# Case 1: 典型 Prefill Decode Cache 拼装managerKVCacheManager(num_blocks10,block_size4,head_dim64)print(初始化内存池...)req1Request(request_id1,prompt_len6)manager.allocate_for_prefill(req1)assertlen(req1.block_table)2,长度 6 的请求应分配 2 个 Blockassertlen(manager.free_blocks)8,池中应该剩下 8 个空闲块print(f✅ Prefill 测试通过Req1 分配的块表:{req1.block_table})manager.allocate_for_decode(req1)assertlen(req1.block_table)2,生成第 7 个 token 时不应该分配新块manager.allocate_for_decode(req1)manager.allocate_for_decode(req1)assertlen(req1.block_table)3,生成第 9 个 token 时应当分配了第 3 个新块assertlen(manager.free_blocks)7,池中应该剩下 7 个空闲块print(f✅ Decode 动态分配测试通过Req1 最新块表:{req1.block_table})forblock_id,valueinzip(req1.block_table,[1.0,2.0,3.0]):manager.physical_kv_cache[block_id].fill_(value)cachemanager.get_physical_cache(req1)assertcache.shape(9,64),f拼装出来的连续 Cache 形状不对应为 (9, 64)实为{cache.shape}asserttorch.all(cache[:4]1.0),第 1 个 Block 未正确拼装asserttorch.all(cache[4:8]2.0),第 2 个 Block 未正确拼装asserttorch.all(cache[8:]3.0),第 3 个 Block 的截断拼装不正确print(✅ Cache 拼装测试通过多块物理缓存被正确恢复为逻辑连续序列。)# Case 2: 恰好跨越 block 边界时Decode 应该分配新块并正确截断最后一块manager2KVCacheManager(num_blocks4,block_size4,head_dim8)req2Request(request_id2,prompt_len4)manager2.allocate_for_prefill(req2)assertlen(req2.block_table)1,长度 4 的请求应只分配 1 个 Blockmanager2.allocate_for_decode(req2)assertlen(req2.block_table)2,长度 5 的请求应分配第 2 个 Blockmanager2.physical_kv_cache[req2.block_table[0]].fill_(7.0)manager2.physical_kv_cache[req2.block_table[1]].fill_(8.0)cache2manager2.get_physical_cache(req2)assertcache2.shape(5,8),f拼装出来的连续 Cache 形状不对应为 (5, 8)实为{cache2.shape}asserttorch.all(cache2[:4]7.0),边界块的前 4 个 token 不正确asserttorch.all(cache2[4:]8.0),边界块的最后 1 个 token 不正确print(✅ 边界分配与截断测试通过)# Case 3: OOM 分支必须抛出 RuntimeErroroom_managerKVCacheManager(num_blocks1,block_size4,head_dim8)oom_reqRequest(request_id3,prompt_len5)try:oom_manager.allocate_for_prefill(oom_req)exceptRuntimeErrorase:assertOOMinstr(e),OOM 异常信息不正确print(✅ OOM 测试通过)else:raiseAssertionError(显存池不足时应该抛出 RuntimeError(OOM))print(\n✅ All Tests Passed! PagedAttention 内存管理逻辑验证通过。)exceptNotImplementedError:print(请先完成 TODO 部分的代码)raiseexcept(AttributeError,NameError,TypeError,ValueError,AssertionError,RuntimeError)ase:ifisinstance(e,AttributeError):print(代码未完成无法找到必要的属性)elifisinstance(e,NameError):print(代码可能未完成导致变量为 NoneType。)elifisinstance(e,TypeError):print(代码可能未完成导致变量为 NoneType。)elifisinstance(e,ValueError):print(代码可能未完成导致了张量维度错误)elifisinstance(e,AssertionError):print(代码可能未完成导致了断言失败)elifisinstance(e,RuntimeError):print(代码可能未完成导致了运行时错误)else:print(代码可能未完成导致了断言失败)raiseNotImplementedError(请先完成 TODO 部分的代码)fromeexceptExceptionase:print(f❌ 测试失败:{e})raisetest_paged_attention_manager()结果SGLang RadixAttention(SGLang基数注意力)如果说 vLLM 的PagedAttention解决了“单请求内部Intra-request显存碎片化”的问题那么 SGLang 提出的RadixAttention基数注意力机制则更进一步解决了“跨请求/多轮交互间Inter-requestKV Cache 的自动复用与生命周期管理”的难题。1. 核心痛点重复 Prefill 与跨请求浪费在大模型实际应用场景中大量请求都包含重叠的前缀Overlapping Prefixes多轮对话Multi-turn Chat第 2 轮对话的输入包含第 1 轮的 Prompt 和 Answer。Agent / 复杂工作流在 Tree-of-Thought思维树或 Monte Carlo 搜索中多个分支共享相同的推导历史。固定 System Prompt / Few-shot 示例万级请求共享同一段很长的规则说明或示例。在传统推理引擎中哪怕请求之间有 90% 的 Token 完全相同新请求到来时系统依然要对其前缀重新做一次Prefill预填充计算不仅浪费算力还会导致首包延迟TTFT, Time-To-First-Token居高不下。2. 核心原理用基数树Radix Tree自动管理 KV CacheSGLang 没有采用复杂的全局 Hash 表而是引入了计算机科学中经典的Radix Tree基数树/压缩前缀树来作为 KV Cache 的索引结构。在 RadixTree 中边Edges与节点Nodes保存连续的 Token 序列如You are a helpful assistant...。指针与物理映射节点直接关联底层物理显存中的 KV Cache 块通常结合了类似 PagedAttention 的 Block 机制。动态生命周期自动匹配Prefix Matching新 Prompt 进来时在树中从根节点向下做最长前缀匹配。匹配到的部分直接复用 KV Cache彻底跳过这部分的 Prefill 计算模型只需要对“新后缀”进行计算。动态分裂Split Insert当新请求在某个节点中途出现分叉时原有节点会自动拆分为一个公共父节点和两个子节点。LRU 淘汰LRU Eviction当 GPU 显存满载时系统会根据 LRU最近最少使用策略优先删除树的叶子节点Leaf Nodes对应的 KV Cache并回收显存直到空间足够。3. 典型应用场景RadixAttention 的最大优势在于“零配置全自动”—— 开发者不需要手动维护复杂的 Cache 清单系统会在后台自动识别模式并完成 KV 共享。在以下四种常见模式中RadixAttention 能带来数倍的吞吐与延迟优化Few-shot Learning少样本提示多个请求共享相同的示例 Prompt前缀 Cache 命中率接近 100%。Multi-turn Chat多轮交互随对话轮数增加历史上下文全部在树上后续轮次只需 Prefill 用户刚发送的一句话。Self-consistency采样一致性单 Prompt 产生多个采样分支前缀只计算一次。Tree-of-Thought思维树搜索多路探索任务中所有子分支自动共享根节点与父节点的搜索历史。4. 对比PagedAttention vs. RadixAttention维度PagedAttention (vLLM)RadixAttention (SGLang)解决的核心问题解决单请求内物理显存离散化与碎片问题解决跨请求间自动前缀复用与调度问题索引数据结构扁平的物理块映射表Block Table动态层级基数树Radix Tree前缀匹配机制主要是单请求/显式配置的前缀缓存全自动最长前缀匹配Automatic Prefix Caching缓存回收策略请求结束即立即释放或简单保留基于树结构的LRU 延迟释放作为全局 Cache 池性能优势点极大提高 Batch Size 与 GPU 利用率极大地降低多轮/Agent 场景的TTFT (首字延迟)一句话总结PagedAttention 提供了高效的“物理内存物理块”管理而 RadixAttention 在其之上盖了一层“逻辑前缀索引树”两者结合成为了现代大模型推理引擎如 SGLang、vLLM v1/v2 架构的标准配置。代码实现importtorchclassTreeNode:def__init__(self,key_tokens):self.key_tokenskey_tokens#这条边上的Token序列如[101,532,789]self.children[]#子节点列表self.kv_cache_ptrNone#模拟指向物理KV Cache的指针classSimpleRadixCache:def__init__(self):#根节点是空的self.rootTreeNode([])definsert(self,tokens):nodeTreeNode(tokens)self.root.children.append(node)def_lcp_len(self,cached_tokens,prompt_tokens):match_len0#TODO1:逐个token计算最长公共前缀长度,遇到不相等时立刻停止match_len0whilematch_lenlen(cached_tokens)andmatch_lenlen(prompt_tokens):ifcached_tokens[match_len]prompt_tokens[match_len]:match_len1else:breakreturnmatch_lendefmatch_prefix(self,prompt_tokens):best_match_len0#TODO2:遍历self.root.children,更新最长匹配前缀长度forchildinself.root.children:match_lenself._lcp_len(child.key_tokens,prompt_tokens)ifmatch_lenbest_match_len:best_match_lenmatch_lenreturnbest_match_lendefsplit_prompt(self,prompt_tokens):#TODO3:先找命中长度再拆出前缀和后缀hit_lenself.match_prefix(prompt_tokens)hit_prefixprompt_tokens[:hit_len]miss_suffixprompt_tokens[hit_len:]returnhit_prefix,miss_suffix,hit_len# 测试你的实现deftest_radix_attention():try:cacheSimpleRadixCache()cache.insert([0,1,2,3])cache.insert([0,1,2,3,4])cache.insert([9,9,9])# 1. 基础 LCP 检查assertcache._lcp_len([1,2,3],[1,2,4])2,LCP 计算失败assertcache._lcp_len([7,8],[7,8,9,10])2,完整前缀匹配失败print(✅ 最长公共前缀计算正确)# 2. 多候选路径下应该选择最长命中前缀match_lencache.match_prefix([0,1,2,3,4,5])assertmatch_len5,匹配失败应该命中最长的 5 个 token 前缀。assertcache.match_prefix([7,6,5])0,错误匹配不该匹配到任何东西。print(✅ 多路径前缀命中选择正确)# 3. 前缀拆分验证hit_prefix,miss_suffix,hit_lencache.split_prompt([0,1,2,3,4,5])asserthit_len5,Hit Length 计算错误asserthit_prefix[0,1,2,3,4],可复用前缀拆分错误assertmiss_suffix[5],待重算后缀拆分错误hit_prefix2,miss_suffix2,hit_len2cache.split_prompt([7,6,5])asserthit_len20,无命中时 Hit Length 应为 0asserthit_prefix2[],无命中时前缀应为空assertmiss_suffix2[7,6,5],无命中时后缀应保持原样print(✅ 前缀拆分与回退逻辑正确)print(\n 所有测试通过这正是 SGLang 让大模型推理首字响应飞升 10 倍的底层秘密)exceptNotImplementedError:print(请先完成 TODO 部分的代码)raiseexcept(AttributeError,NameError,TypeError,ValueError,AssertionError,RuntimeError)ase:ifisinstance(e,AttributeError):print(代码未完成无法找到必要的属性)elifisinstance(e,NameError):print(代码可能未完成导致了变量未定义)elifisinstance(e,TypeError):print(代码可能未完成导致了操作错误)elifisinstance(e,ValueError):print(代码可能未完成导致了张量维度错误)elifisinstance(e,AssertionError):print(代码可能未完成导致了断言失败)elifisinstance(e,RuntimeError):print(代码可能未完成导致了运行时错误)else:print(代码可能未完成导致了断言失败)raiseNotImplementedError(请先完成 TODO 部分的代码)fromeexceptExceptionase:print(f❌ 发生未知异常:{e})raisetest_radix_attention()结果

相关新闻

PCB设计制造“残铜率”知多少,怎么做好控制±5%的阻抗值

PCB设计制造“残铜率”知多少,怎么做好控制±5%的阻抗值

高速先生成员--黄刚随着我司的板厂制程越来越成熟,工艺能力自然也越来越强。目前在阻抗加工精度这块,我们可以非常自信的和大家宣传在非电镀的内层走线可以实现5%的精度,着实让我们的客户感到满满的幸福,在同样的设计下&#xff0…

2026/7/22 14:28:18 阅读更多 →
Seedance 2.0:AI原生4K视频生成技术与多模态输入实战指南

Seedance 2.0:AI原生4K视频生成技术与多模态输入实战指南

如果你还在为制作高质量视频内容而头疼——无论是产品宣传、社交媒体内容,还是创意短片——那么今天要介绍的 Seedance 2.0 可能正是你需要的解决方案。这个由字节跳动开发、通过 Higgsfield 平台提供的 AI 视频生成模型,正在重新定义 4K 视频创作的门槛…

2026/7/22 14:28:18 阅读更多 →
变色丝带 同花顺期货通指标

变色丝带 同花顺期货通指标

今天给大家带来是一款同花顺期货通指标,并且已经上架到同花顺期货通的指标广场上了。喜欢的朋友可以去指标广场安装试用!!友情提示:(指标只是辅助,不作建议)拼多多店铺:指标公式编写…

2026/7/22 14:28:18 阅读更多 →

最新新闻

Solarized主题全家桶:dotfiles44/dotfiles实现vim+tmux+iterm配色统一

Solarized主题全家桶:dotfiles44/dotfiles实现vim+tmux+iterm配色统一

Solarized主题全家桶:dotfiles44/dotfiles实现vimtmuxiterm配色统一 【免费下载链接】dotfiles A set of vim, zsh, git, and tmux configuration files. 项目地址: https://gitcode.com/gh_mirrors/dotfiles44/dotfiles dotfiles44/dotfiles是一套集成vim、…

2026/7/22 19:12:53 阅读更多 →
三星 Z Fold 8 Ultra 即将发布:延续纤长设计,5000mAh 电池视频播放最长 27 小时

三星 Z Fold 8 Ultra 即将发布:延续纤长设计,5000mAh 电池视频播放最长 27 小时

三星 Z Fold 8 Ultra:纤长设计与续航升级距离三星新品发布会仅剩几个小时,关于 Z Fold 8 Ultra 的爆料不断。这款手机延续了 Z Fold 7 纤长的设计风格,同时配备了升级版的 5000mAh 电池,视频播放时长最高可达 27 小时。满足用户续…

2026/7/22 19:12:53 阅读更多 →
GPS与mission规划完全手册:INAV Configurator实现自主返航与航点飞行

GPS与mission规划完全手册:INAV Configurator实现自主返航与航点飞行

GPS与mission规划完全手册:INAV Configurator实现自主返航与航点飞行 【免费下载链接】inav-configurator 项目地址: https://gitcode.com/gh_mirrors/in/inav-configurator INAV Configurator是一款功能强大的开源地面站软件,专为无人机爱好者和…

2026/7/22 19:12:53 阅读更多 →
为什么顶尖研究员都在用Kimi读论文网页?5个专业级操作链路,普通用户至今没解锁

为什么顶尖研究员都在用Kimi读论文网页?5个专业级操作链路,普通用户至今没解锁

更多请点击: https://intelliparadigm.com 第一章:Kimi网页阅读的底层逻辑与认知跃迁 Kimi网页阅读并非简单的文本抓取与渲染,其核心在于多模态语义理解与上下文感知式信息重构。当用户输入URL时,Kimi首先调用无头浏览器引擎&…

2026/7/22 19:12:53 阅读更多 →
航空发动机叶片焊完就裂?精密热控制的三把钥匙

航空发动机叶片焊完就裂?精密热控制的三把钥匙

所谓航空发动机叶片激光焊接修复,就是用高能量密度的激光束在高温合金叶片损伤区域熔化填充材料,同时精确控制热输入,使修复后的叶片在1100C以上的燃气环境中仍能保持原有的疲劳寿命和气动外形。一台现代航空发动机内部,涡轮叶片的…

2026/7/22 19:12:53 阅读更多 →
Peaclock开发者指南:从源码构建到依赖管理的完整流程

Peaclock开发者指南:从源码构建到依赖管理的完整流程

Peaclock开发者指南:从源码构建到依赖管理的完整流程 【免费下载链接】peaclock A responsive and customizable clock, timer, and stopwatch for the terminal. 项目地址: https://gitcode.com/gh_mirrors/pe/peaclock Peaclock是一款功能强大的终端时钟工…

2026/7/22 19:11:52 阅读更多 →

日新闻

TI DSP系统配置模块SYSCFG详解:中断机制与主设备优先级配置实战

TI DSP系统配置模块SYSCFG详解:中断机制与主设备优先级配置实战

1. 项目概述与SYSCFG模块的核心价值在嵌入式系统,尤其是像TI C6000系列这样的高性能DSP开发中,我们常常会与芯片手册里那些密密麻麻的寄存器打交道。很多开发者可能更关注算法实现、内存优化或者外设驱动,但对于一个稳定、高效的系统而言&…

2026/7/22 0:00:26 阅读更多 →
微信Server酱:高到达率的应急通知方案实践

微信Server酱:高到达率的应急通知方案实践

1. 为什么我们需要"最次"的通知方案? 在数字化协作环境中,消息通知系统的重要性不言而喻明。但现实情况是,企业级通知方案往往需要复杂的API对接(如企业微信、钉钉、飞书),个人开发者的小项目又经…

2026/7/22 0:00:26 阅读更多 →
甲方要的“简洁“PPT,到底是简洁还是省事?

甲方要的“简洁“PPT,到底是简洁还是省事?

甲方说"简洁一点",乙方听到的是"少做几页"。甲方说"不要太复杂",乙方理解成"别放图表了"。结果交过去,甲方说"我说的简洁不是这个意思"。"简洁"这个词在PPT语境里,是…

2026/7/22 0:00:26 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/22 8:58:19 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/21 5:34:47 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/22 12:54:44 阅读更多 →

月新闻